Skip to content

Commit

Permalink
Fixes a null pointer crash (Github Issue #269)
Browse files Browse the repository at this point in the history
1. Fixes a null pointer in TranscoderUtils when retrieving the duration value from a track by checking if the field is present before accessing it.
2. Updated logic of estimateVideoTrackBitrate API in TranscoderUtils to utilize the source media file duration in cases where track specific duration value is null.
3. Updated logic of createTargetMediaFormat API in MediaTransformer class to use a default bit rate of 10_000_000 when we fail to estimate a valid bitrate from the source media track.
  • Loading branch information
vamshi-dhulipala committed Jan 18, 2024
1 parent 6f4fb88 commit 0b18b4f
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class MediaTransformer {

public static final int DEFAULT_KEY_FRAME_INTERVAL = 5;
private static final int DEFAULT_AUDIO_BITRATE = 256_000;
private static final int DEFAULT_VIDEO_BITRATE = 10_000_000;
private static final int DEFAULT_FRAME_RATE = 30;

private static final String TAG = MediaTransformer.class.getSimpleName();
Expand Down Expand Up @@ -412,6 +413,10 @@ private MediaFormat createTargetMediaFormat(@NonNull MediaSource mediaSource,
sourceMediaFormat.getInteger(MediaFormat.KEY_WIDTH),
sourceMediaFormat.getInteger(MediaFormat.KEY_HEIGHT));
int targetBitrate = TranscoderUtils.estimateVideoTrackBitrate(mediaSource, sourceTrackIndex);
if (targetBitrate <= 0) {
// Use a default value in case of failure to extract value from source media
targetBitrate = DEFAULT_VIDEO_BITRATE;
}
targetMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, targetBitrate);

int targetKeyFrameInterval = DEFAULT_KEY_FRAME_INTERVAL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class MediaExtractorMediaSource implements MediaSource {

private int orientationHint;
private long size;
private final long duration;

public MediaExtractorMediaSource(@NonNull Context context, @NonNull Uri uri) throws MediaSourceException {
this(context, uri, new MediaRange(0, Long.MAX_VALUE));
Expand All @@ -52,6 +53,8 @@ public MediaExtractorMediaSource(@NonNull Context context, @NonNull Uri uri, @No
if (rotation != null) {
orientationHint = Integer.parseInt(rotation);
}
String durationStr = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
duration = (durationStr != null) ? Long.parseLong(durationStr) : -1L;
size = TranscoderUtils.getSize(context, uri);
// Release unused anymore MediaMetadataRetriever instance
releaseQuietly(mediaMetadataRetriever);
Expand Down Expand Up @@ -124,6 +127,11 @@ public MediaRange getSelection() {
return mediaRange;
}

@Override
public long getDuration() {
return duration;
}

private void releaseQuietly(MediaMetadataRetriever mediaMetadataRetriever) {
try {
mediaMetadataRetriever.release();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,13 @@ public interface MediaSource {
default MediaRange getSelection() {
return new MediaRange(0, Long.MAX_VALUE);
}

/**
* Returns the playback duration of the media if applicable and known
*
* @return playback duration in milliseconds, -1 if unknown
*/
default long getDuration() {
return -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ public static int estimateVideoTrackBitrate(@NonNull MediaSource mediaSource, in
if (videoTrackFormat.containsKey(MediaFormat.KEY_BIT_RATE)) {
return videoTrackFormat.getInteger(MediaFormat.KEY_BIT_RATE);
}
float videoTrackDuration = TimeUtils.microsToSeconds(videoTrackFormat.getLong(MediaFormat.KEY_DURATION));
if (videoTrackDuration == 0) {
return 0;
float videoTrackDuration = estimateVideoTrackDuration(mediaSource, videoTrackFormat);
if (videoTrackDuration <= 0) {
return -1;
}

float unallocatedSize = mediaSource.getSize();
Expand All @@ -149,9 +149,12 @@ public static int estimateVideoTrackBitrate(@NonNull MediaSource mediaSource, in
} else {
String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
if (mimeType.startsWith("video")) {
totalPixels += trackFormat.getInteger(MediaFormat.KEY_WIDTH)
* trackFormat.getInteger(MediaFormat.KEY_HEIGHT)
* TimeUtils.microsToSeconds(trackFormat.getLong(MediaFormat.KEY_DURATION));
float trackDuration = estimateVideoTrackDuration(mediaSource, trackFormat);
if (trackDuration > 0) {
totalPixels += trackFormat.getInteger(MediaFormat.KEY_WIDTH)
* trackFormat.getInteger(MediaFormat.KEY_HEIGHT)
* trackDuration;
}
}
}
}
Expand All @@ -165,6 +168,23 @@ public static int estimateVideoTrackBitrate(@NonNull MediaSource mediaSource, in
return (int) (trackSize * BITS_IN_BYTE / videoTrackDuration);
}

/**
* Returns the duration of the given video track. If the given track does not contain a valid
* duration value in its meta data, then the duration value associated with the media source
* shall be returned.
*
* @param mediaSource {@link MediaSource} which contains the video track
* @param videoTrack Track whose duration needs to be determined
*
* @return Duration of the given video track in seconds.
*/
@VisibleForTesting
static float estimateVideoTrackDuration(MediaSource mediaSource, MediaFormat videoTrack) {
return videoTrack.containsKey(MediaFormat.KEY_DURATION) ?
TimeUtils.microsToSeconds(videoTrack.getLong(MediaFormat.KEY_DURATION)) :
TimeUtils.millisToSeconds(mediaSource.getDuration());
}

/**
* Get size of data abstracted by uri
* @param context context to access uri
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,34 @@ public void useVideoTrackBitrateAsEstimationWhenPresent() {
assertThat(estimatedBitrate, is(VIDEO_BIT_RATE));
}

@Test
public void useMediaDurationForVideoTrackBitrateEstimationWhenTrackMetadataDoesNotExist() {
long mediaDuration = 50000L; int bitrateUsingMediaDuration = 11400000;
when(mediaSource.getTrackFormat(0)).thenReturn(videoMediaFormat);
when(mediaSource.getDuration()).thenReturn(mediaDuration);
when(videoMediaFormat.containsKey(MediaFormat.KEY_BIT_RATE)).thenReturn(false);
when(videoMediaFormat.containsKey(MediaFormat.KEY_DURATION)).thenReturn(false);

when(mediaSource.getSize()).thenReturn(VIDEO_BIT_RATE * DURATION_S / 8);
when(mediaSource.getTrackCount()).thenReturn(1);

int estimatedBitrate = TranscoderUtils.estimateVideoTrackBitrate(mediaSource, 0);

assertThat(estimatedBitrate, is(bitrateUsingMediaDuration));
}

@Test
public void returnInvalidBitrateWhenNeitherTrackDurationNorMediaDurationIsAvailable() {
int invalidBitrate = -1;
when(mediaSource.getTrackFormat(0)).thenReturn(videoMediaFormat);
when(videoMediaFormat.containsKey(MediaFormat.KEY_BIT_RATE)).thenReturn(false);
when(videoMediaFormat.containsKey(MediaFormat.KEY_DURATION)).thenReturn(false);
when(mediaSource.getDuration()).thenReturn((long) invalidBitrate);

int estimatedBitrate = TranscoderUtils.estimateVideoTrackBitrate(mediaSource, 0);
assertThat(estimatedBitrate, is(invalidBitrate));
}

@Test
public void estimateVideoTrackBitrateWhenSingleVideoTrack() {
when(mediaSource.getSize()).thenReturn(VIDEO_BIT_RATE * DURATION_S / 8);
Expand Down Expand Up @@ -250,6 +278,20 @@ public void estimateVideoTrackBitrateWhenMultipleVideoTracks() {
assertEquals(estimatedBitrate, VIDEO_BIT_RATE, 1);
}

@Test
public void estimateVideoTrackDurationFromMetadata() {
float durationSecs = TranscoderUtils.estimateVideoTrackDuration(mediaSource, videoMediaFormat);
assertThat(durationSecs, is((float) DURATION_S));
}

@Test
public void useMediaDurationAsFallbackWhenEstimatingVideoTrackDuration() {
long mediaDuration = 50_000L;
when(mediaSource.getDuration()).thenReturn(mediaDuration);
when(videoMediaFormat.containsKey(MediaFormat.KEY_DURATION)).thenReturn(false);
float durationSecs = TranscoderUtils.estimateVideoTrackDuration(mediaSource, videoMediaFormat);
assertThat(durationSecs, is((float) mediaDuration/1000));
}

@Test
public void estimateWhenTrimmedFromBeginningToMiddle() {
Expand Down

0 comments on commit 0b18b4f

Please sign in to comment.