Skip to content

Commit

Permalink
get the H264 SPS and PPS from the first frame
Browse files Browse the repository at this point in the history
* might fix some codec initialization issues (see google#4)
  • Loading branch information
Martin Bonnin committed Aug 26, 2014
1 parent 0ba19f1 commit 36a305d
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.parser.aac.AACExtractor;
import com.google.android.exoplayer.parser.h264.H264Utils;
import com.google.android.exoplayer.parser.ts.TSExtractorWithParsers;
import com.google.android.exoplayer.upstream.AESDataSource;
import com.google.android.exoplayer.upstream.DataSource;
Expand All @@ -26,11 +27,14 @@
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicLong;
import com.google.android.exoplayer.parser.h264.H264Utils.SPS;

/**
* Created by martin on 31/07/14.
Expand Down Expand Up @@ -458,7 +462,6 @@ public int readData(int track, long playbackPositionUs, FormatHolder formatHolde
sampleHolder.flags = MediaExtractor.SAMPLE_FLAG_SYNC;
bufferSize -= sampleHolder.size;
//Log.d(TAG, String.format("del %6d => %6d", sampleHolder.size, bufferSize));
//Log.d(TAG, (sample.type == HLSExtractor.TYPE_AUDIO ? "AUDIO" : "VIDEO") + " timeUS=" + (sampleHolder.pts/1000));
return SAMPLE_READ;
}
} catch (NoSuchElementException e) {
Expand Down Expand Up @@ -576,6 +579,7 @@ protected Void doInBackground(Void... params) {
Log.d(TAG, "opening " + chunkUrl);
Uri uri = null;
MediaFormat audioMediaFormat = null;
MediaFormat videoMediaFormat = null;

if (variantEntry.keyEntry != null) {
String dataUrl = null;
Expand All @@ -597,15 +601,6 @@ protected Void doInBackground(Void... params) {
uri = Uri.parse(chunkUrl);
}

synchronized (source.list) {
if (!aborted) {
ChunkSentinel sentinel = new ChunkSentinel();
sentinel.mediaFormat = chunk.videoMediaFormat;
sentinel.entry = chunk.mainEntry;
list.get(Packet.TYPE_VIDEO).add(sentinel);
}
}

DataSpec dataSpec = new DataSpec(uri, variantEntry.offset, variantEntry.length, null);
DataSource HTTPDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
DataSource dataSource = new AESDataSource(userAgent, HTTPDataSource);
Expand Down Expand Up @@ -662,6 +657,7 @@ protected Void doInBackground(Void... params) {
videoStreamType = extractor.getStreamType(Packet.TYPE_VIDEO);
gotStreamTypes = true;
}

if (audioMediaFormat == null && sample.type == Packet.TYPE_AUDIO) {
if (audioStreamType == Extractor.STREAM_TYPE_AAC_ADTS) {
AACExtractor.ADTSHeader h = new AACExtractor.ADTSHeader();
Expand All @@ -681,12 +677,26 @@ protected Void doInBackground(Void... params) {
sentinel.entry = chunk.mainEntry;

list.get(sample.type).add(sentinel);
} else if (videoMediaFormat == null && sample.type == Packet.TYPE_VIDEO) {
ChunkSentinel sentinel = new ChunkSentinel();
List<byte[]> csd = new ArrayList<byte []>();
SPS sps = H264Utils.extractSPS_PPS(sample.data, csd);
if (sps != null) {
// some decoders need the Codec Specific Data
sentinel.mediaFormat = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
sps.width, sps.height, csd);
} else {
sentinel.mediaFormat = chunk.videoMediaFormat;
}
videoMediaFormat = sentinel.mediaFormat;
sentinel.entry = chunk.mainEntry;
list.get(Packet.TYPE_VIDEO).add(sentinel);
}

list.get(sample.type).add(sample);

bufferSize += sample.data.position();
//Log.d(TAG, String.format("add %6d => %6d", sample.data.limit(), bufferSize));
//Log.d(TAG, (sample.type == Packet.TYPE_AUDIO ? "AUDIO" : "VIDEO") + " timeUS=" + (sample.pts/45) + " size=" + sample.data.position());
}
source.bufferedPts.set(sample.pts);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.google.android.exoplayer.parser.h264;

import android.util.Log;

import java.nio.ByteBuffer;
import java.util.List;

/**
* Created by martin on 25/08/14.
*/
public class H264Utils {
public static class SPS {
public int profile_idc;
public int level_idc;
public int width;
public int height;
public int seq_parameter_set_id;
public int log2_max_frame_num_minus4;
public int pic_order_cnt_type;
public int log2_max_pic_order_cnt_lsb_minus4;
public int num_ref_frames_in_pic_order_cnt_cycle;
public int num_ref_frames;
public int mb_width;
public int mb_height;
public int crop_left;
public int crop_right;
public int crop_top;
public int crop_bottom;
}

public final static int NAL_SPS = 7;
public final static int NAL_PPS = 8;

static class BitReader {
private int offset;
private ByteBuffer data;
private int position;
public int currentByte;
public BitReader(ByteBuffer data, int offset) {
this.data = data;
this.offset = offset;

position = -1;
}

public int read(int count) {
int ret = 0;
while (count-- > 0) {
ret <<= 1;

if (position == -1) {
currentByte = data.get(offset);
offset++;
position = 7;
}

ret |= ((currentByte & (1 << position)) > 0) ? 1 : 0;
position--;
}

return ret;
}

public int readUnsignedExpGolomb() {
int trailingBits = 0;
while (read(1) == 0) {
trailingBits++;
}

return (1 << trailingBits | read(trailingBits)) - 1;
}
}


public static SPS parseSPS(ByteBuffer data, int offset) {
int i = offset;
SPS sps = new SPS();

sps.profile_idc = data.get(i++);
// reserved
i++;
sps.level_idc = data.get(i++);

BitReader reader = new BitReader(data, i);
sps.seq_parameter_set_id = reader.readUnsignedExpGolomb();
sps.log2_max_frame_num_minus4 = reader.readUnsignedExpGolomb();
sps.pic_order_cnt_type = reader.readUnsignedExpGolomb();
if (sps.pic_order_cnt_type == 0) {
sps.log2_max_pic_order_cnt_lsb_minus4 = reader.readUnsignedExpGolomb();
} else if (sps.pic_order_cnt_type == 0) {
reader.read(1);
// these should read signed exp golombs but since I don't care about
// the value, I just use the unsigned version
reader.readUnsignedExpGolomb();
reader.readUnsignedExpGolomb();
sps.num_ref_frames_in_pic_order_cnt_cycle = reader.readUnsignedExpGolomb();
for (int j = 0; j < sps.num_ref_frames_in_pic_order_cnt_cycle; j++) {
reader.readUnsignedExpGolomb();
}
}
sps.num_ref_frames = reader.readUnsignedExpGolomb();
reader.read(1);
sps.mb_width = (reader.readUnsignedExpGolomb() + 1) * 16;
sps.mb_height = (reader.readUnsignedExpGolomb() + 1) * 16;

int frames_mbs_only_flag = reader.read(1);
if (frames_mbs_only_flag == 0) {
reader.read(1); //mb_adaptive_frame_field_flag
}
reader.read(1); //direct_8x8_inference_flag
int frame_cropping_flag = reader.read(1);
if (frame_cropping_flag == 1) {
sps.crop_left = reader.readUnsignedExpGolomb();
sps.crop_right = reader.readUnsignedExpGolomb();
sps.crop_top = reader.readUnsignedExpGolomb();
sps.crop_bottom = reader.readUnsignedExpGolomb();
sps.width = sps.mb_width - (sps.crop_right + sps.crop_left);
sps.height = sps.mb_height - (sps.crop_bottom + sps.crop_top);
} else {
sps.width = sps.mb_width;
sps.height = sps.mb_height;
}


return sps;
}

public static void dumpNALs(ByteBuffer data) {
int i = 0;
int limit = data.limit();
while (i < limit - 4) {
if (data.get(i) == 0 && data.get(i+1) == 0 && data.get(i+2) == 1) {
int type = (int)(data.get(i+3))&0x1f;
Log.d("NAL", String.format("H264NAL(@%8d): %2d -> %s", i, type, getNALName(type)));
}
i++;
}
}

public static String getNALName(int type) {
switch(type) {
case 1: return "slice of non-IDR";
case 2: return "slice of data partition A";
case 3: return "slice of data partition B";
case 4: return "slice of data partition C";
case 5: return "slice of IDR";
case 6: return "SEI";
case NAL_SPS: return "SPS";
case NAL_PPS: return "PPS";
case 9: return "Access unit delimiter";
case 10: return "end of sequence";
case 11: return "end of stream";
default: return "?";
}
}

public static SPS extractSPS_PPS(ByteBuffer data, List<byte[]> csd) {
int i = 0;
int size = data.position();
byte ppsData[] = null;
byte spsData[] = null;

while (i < size - 4) {
if (data.get(i) == 0 && data.get(i + 1) == 0 && data.get(i + 2) == 1) {
int type = (int) (data.get(i + 3)) & 0x1f;
int start = i;

i += 4;

while (i < size - 3) {
if (data.get(i) == 0 && data.get(i + 1) == 0 && (data.get(i + 2) == 1 || data.get(i + 2) == 0)) {
break;
}
i++;
}
if (i == size - 3) {
i = size;
}

if (type == NAL_PPS && ppsData == null) {
ppsData = new byte[i - start];
int oldPosition = data.position();
data.position(start);
data.get(ppsData, 0, i - start);
data.position(oldPosition);
csd.add(ppsData);
} else if (type == NAL_SPS && spsData == null) {
spsData = new byte[i - start];
int oldPosition = data.position();
data.position(start);
data.get(spsData, 0, i - start);
data.position(oldPosition);
csd.add(spsData);
}
Log.d("NAL", String.format("H264NAL(@%8d): %2d -> %s", i, type, getNALName(type)));

if (spsData != null && ppsData != null) {
break;
}
}
i++;
}

if (ppsData != null) {
return parseSPS(ByteBuffer.wrap(spsData), 4);
} else {
return null;
}
}
}

0 comments on commit 36a305d

Please sign in to comment.