NullPointerException when threading MediaMetadataRetriever [Android]

1.6k views Asked by At

I have once again run out of ideas for an error and returned to the helpful Stack Overflow community for guidance. I understand that this is a very long post to wade through, but I believe that it could be helpful to others in the community, and provide a challenge for veterans.

Essentially, I am building an app that processes a video frame by frame. Initially, this process was done linearly, but speed is a major concern for this application. The natural thought was that seperate threads could be implemented to process the frames. Each thread would simply be a single iteration of the previous loop; retrieving the frame from the video, processing it, and placing the resulting data into the correct index of an array.

The issue is that during the transition from a loop to threads, a NullPointerException is sometimes thrown (Roughly once every 200 frames), when attempting to access the frame returned by MediaMetadataRetriever.getFrameAtTime()

Here are some sections of the code that may be of use:

Excerpt from the onClickListener attached to the button that starts the processing. The onClickListener starts a ThreadPoolExecutor which manages the threads for frame processing.

long videoLength = getVideoLength(videoUri);
coords = new coordinate[(int)(videoLength/frameIntervalInMicroSeconds)];
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    Runtime.getRuntime().availableProcessors()+1,
    Runtime.getRuntime().availableProcessors()+1,
    1,
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<Runnable>());
for (long a = 0; a < videoLength; a += frameIntervalInMicroSeconds) {
    new ProcessFrame().executeOnExecutor(executor, (long) a);

getVideoFrame(), a helper method that takes in a Uri for a video, and a time in microseconds, and returns the frame of the video as a Bitmap

private Bitmap getVideoFrame(Uri uri, long timeInUSeconds) {
    MediaMetadataRetriever retriever = new MediaMetadataRetriever();
    retriever.setDataSource(this, uri);
    Bitmap temp = retriever.getFrameAtTime(timeInUSeconds, MediaMetadataRetriever.OPTION_CLOSEST);
    retriever.release();
    return temp;
}

Excerpt from ProcessFrame, the thread (as an ASyncTask) that is run by the ThreadPoolExecutor. As mentioned above, the thread simply gets the frame, processes the frame, and places it into the correct location in the array.

protected Void doInBackground(Long... frameTime){
    /* Excluded variable declarations */

    Bitmap bMap;
    synchronized (this) {
        bMap = getVideoFrame(videoUri, frameTime[0]);
    }
    try {
        //This line below is where the NullPointerException is thrown
        bMap = Bitmap.createScaledBitmap(bMap, bMap.getWidth() / RESIZE_FACTOR, bMap.getHeight() / RESIZE_FACTOR, true);
    }catch (Exception e){
        System.out.println("Caught NullPointerException");
        return null;
    }

    /* I excluded the frame processing done here */

    //add coordinate to the list
    try {
        synchronized (this){
            coords[(int) (frameTime[0] / frameIntervalInMicroSeconds)] = temp;
        }
    }catch (Exception e){
        System.out.println("Caught out of bounds coordinate");
    }

    /* excluded clean up */
}

Error message produced when a null frame is accessed:

7043-7856/com.example.tyler.laserphysicsappb E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer

General comments, observations, and things I have tried:

The line where the exception is thrown is marked with a comment in the last code block.

Without the try/catch block, my phone does not not display the normal app crashed message on this exception, it flashes a black screen, and then quickly returns to the home screen. (I discovered which line it was by quickly taking a screenshot of logcat while the black screen flashed)

Without the try/catch block, another phone that I tried simply ignores the error and continues, but this ruins the results.

Adding the synchronized block around bMap = getVideoFrame(videoUri, frameTime[0]) seems to have made the error less common, but it still happens.

I have tried the ffmpegMediaMetadataRetriever alternative. This package did not eliminate the error, and slowed down the processing time.

Once the nullPointerException has been thrown, all subsequent threads seem to be more likely to throw the exception again.

Running the ThreadPoolExecutor with a maximum of one thread does not produce the error.

Even if I enclose the entire body of doInBackground() in a synchronize block, the error still appears

1

There are 1 answers

0
TyN101 On BEST ANSWER

I have been able to fix the bug, but with lackluster results. The only way I have found to stop the exception from appearing is by putting a synchronize block around the MediaMetadataRetriever getFrameAtTime() function call. The new getVideoFrame method is below.

private Bitmap getVideoFrame(Uri uri, long timeInUSeconds) {
    MediaMetadataRetriever retriever = new MediaMetadataRetriever();
    retriever.setDataSource(this, uri);
    synchronize (this) {
        Bitmap temp = retriever.getFrameAtTime(timeInUSeconds, MediaMetadataRetriever.OPTION_CLOSEST);
    }
    retriever.release();
    return temp;
}

Sadly, as this fixes the bug, the speed concern is not aleviated, as getting the frame still takes a substantial amount of time, far more so than actually processing the frames. Nevertheless, the pr