Support mpeg1,2 audio and mpeg1,2,4 video content extraction from .ts streams.
Change-Id: I9d2ee63495f161e30daba7c3aab16cb9d8ced6a5
This commit is contained in:
@@ -26,6 +26,7 @@ extern const char *MEDIA_MIMETYPE_VIDEO_VPX;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_AVC;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_MPEG4;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_H263;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_MPEG2;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_RAW;
|
||||
|
||||
extern const char *MEDIA_MIMETYPE_AUDIO_AMR_NB;
|
||||
|
||||
@@ -77,7 +77,7 @@ void NuPlayer::HTTPLiveSource::start() {
|
||||
|
||||
sp<MetaData> NuPlayer::HTTPLiveSource::getFormat(bool audio) {
|
||||
ATSParser::SourceType type =
|
||||
audio ? ATSParser::MPEG2ADTS_AUDIO : ATSParser::AVC_VIDEO;
|
||||
audio ? ATSParser::AUDIO : ATSParser::VIDEO;
|
||||
|
||||
sp<AnotherPacketSource> source =
|
||||
static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
|
||||
@@ -131,7 +131,7 @@ bool NuPlayer::HTTPLiveSource::feedMoreTSData() {
|
||||
status_t NuPlayer::HTTPLiveSource::dequeueAccessUnit(
|
||||
bool audio, sp<ABuffer> *accessUnit) {
|
||||
ATSParser::SourceType type =
|
||||
audio ? ATSParser::MPEG2ADTS_AUDIO : ATSParser::AVC_VIDEO;
|
||||
audio ? ATSParser::AUDIO : ATSParser::VIDEO;
|
||||
|
||||
sp<AnotherPacketSource> source =
|
||||
static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
|
||||
|
||||
@@ -87,7 +87,7 @@ bool NuPlayer::StreamingSource::feedMoreTSData() {
|
||||
|
||||
sp<MetaData> NuPlayer::StreamingSource::getFormat(bool audio) {
|
||||
ATSParser::SourceType type =
|
||||
audio ? ATSParser::MPEG2ADTS_AUDIO : ATSParser::AVC_VIDEO;
|
||||
audio ? ATSParser::AUDIO : ATSParser::VIDEO;
|
||||
|
||||
sp<AnotherPacketSource> source =
|
||||
static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
|
||||
@@ -102,7 +102,7 @@ sp<MetaData> NuPlayer::StreamingSource::getFormat(bool audio) {
|
||||
status_t NuPlayer::StreamingSource::dequeueAccessUnit(
|
||||
bool audio, sp<ABuffer> *accessUnit) {
|
||||
ATSParser::SourceType type =
|
||||
audio ? ATSParser::MPEG2ADTS_AUDIO : ATSParser::AVC_VIDEO;
|
||||
audio ? ATSParser::AUDIO : ATSParser::VIDEO;
|
||||
|
||||
sp<AnotherPacketSource> source =
|
||||
static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
|
||||
|
||||
@@ -909,6 +909,8 @@ status_t ACodec::setupVideoDecoder(
|
||||
compressionFormat = OMX_VIDEO_CodingMPEG4;
|
||||
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) {
|
||||
compressionFormat = OMX_VIDEO_CodingH263;
|
||||
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG2, mime)) {
|
||||
compressionFormat = OMX_VIDEO_CodingMPEG2;
|
||||
} else {
|
||||
TRESPASS();
|
||||
}
|
||||
@@ -1647,6 +1649,10 @@ void ACodec::UninitializedState::onSetup(
|
||||
componentName = "OMX.google.aac.decoder";
|
||||
} else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_MPEG)) {
|
||||
componentName = "OMX.Nvidia.mp3.decoder";
|
||||
} else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_MPEG2)) {
|
||||
componentName = "OMX.Nvidia.mpeg2v.decode";
|
||||
} else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_MPEG4)) {
|
||||
componentName = "OMX.google.mpeg4.decoder";
|
||||
} else {
|
||||
TRESPASS();
|
||||
}
|
||||
@@ -1670,7 +1676,8 @@ void ACodec::UninitializedState::onSetup(
|
||||
mCodec->configureCodec(mime.c_str(), msg);
|
||||
|
||||
sp<RefBase> obj;
|
||||
if (msg->findObject("native-window", &obj)) {
|
||||
if (msg->findObject("native-window", &obj)
|
||||
&& strncmp("OMX.google.", componentName.c_str(), 11)) {
|
||||
sp<NativeWindowWrapper> nativeWindow(
|
||||
static_cast<NativeWindowWrapper *>(obj.get()));
|
||||
CHECK(nativeWindow != NULL);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "include/MP3Extractor.h"
|
||||
|
||||
#include "include/avc_utils.h"
|
||||
#include "include/ID3.h"
|
||||
#include "include/VBRISeeker.h"
|
||||
#include "include/XINGSeeker.h"
|
||||
@@ -44,158 +45,6 @@ namespace android {
|
||||
// Yes ... there are things that must indeed match...
|
||||
static const uint32_t kMask = 0xfffe0c00;
|
||||
|
||||
// static
|
||||
bool MP3Extractor::get_mp3_frame_size(
|
||||
uint32_t header, size_t *frame_size,
|
||||
int *out_sampling_rate, int *out_channels,
|
||||
int *out_bitrate, int *out_num_samples) {
|
||||
*frame_size = 0;
|
||||
|
||||
if (out_sampling_rate) {
|
||||
*out_sampling_rate = 0;
|
||||
}
|
||||
|
||||
if (out_channels) {
|
||||
*out_channels = 0;
|
||||
}
|
||||
|
||||
if (out_bitrate) {
|
||||
*out_bitrate = 0;
|
||||
}
|
||||
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 1152;
|
||||
}
|
||||
|
||||
if ((header & 0xffe00000) != 0xffe00000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned version = (header >> 19) & 3;
|
||||
|
||||
if (version == 0x01) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned layer = (header >> 17) & 3;
|
||||
|
||||
if (layer == 0x00) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned protection = (header >> 16) & 1;
|
||||
|
||||
unsigned bitrate_index = (header >> 12) & 0x0f;
|
||||
|
||||
if (bitrate_index == 0 || bitrate_index == 0x0f) {
|
||||
// Disallow "free" bitrate.
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned sampling_rate_index = (header >> 10) & 3;
|
||||
|
||||
if (sampling_rate_index == 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const int kSamplingRateV1[] = { 44100, 48000, 32000 };
|
||||
int sampling_rate = kSamplingRateV1[sampling_rate_index];
|
||||
if (version == 2 /* V2 */) {
|
||||
sampling_rate /= 2;
|
||||
} else if (version == 0 /* V2.5 */) {
|
||||
sampling_rate /= 4;
|
||||
}
|
||||
|
||||
unsigned padding = (header >> 9) & 1;
|
||||
|
||||
if (layer == 3) {
|
||||
// layer I
|
||||
|
||||
static const int kBitrateV1[] = {
|
||||
32, 64, 96, 128, 160, 192, 224, 256,
|
||||
288, 320, 352, 384, 416, 448
|
||||
};
|
||||
|
||||
static const int kBitrateV2[] = {
|
||||
32, 48, 56, 64, 80, 96, 112, 128,
|
||||
144, 160, 176, 192, 224, 256
|
||||
};
|
||||
|
||||
int bitrate =
|
||||
(version == 3 /* V1 */)
|
||||
? kBitrateV1[bitrate_index - 1]
|
||||
: kBitrateV2[bitrate_index - 1];
|
||||
|
||||
if (out_bitrate) {
|
||||
*out_bitrate = bitrate;
|
||||
}
|
||||
|
||||
*frame_size = (12000 * bitrate / sampling_rate + padding) * 4;
|
||||
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 384;
|
||||
}
|
||||
} else {
|
||||
// layer II or III
|
||||
|
||||
static const int kBitrateV1L2[] = {
|
||||
32, 48, 56, 64, 80, 96, 112, 128,
|
||||
160, 192, 224, 256, 320, 384
|
||||
};
|
||||
|
||||
static const int kBitrateV1L3[] = {
|
||||
32, 40, 48, 56, 64, 80, 96, 112,
|
||||
128, 160, 192, 224, 256, 320
|
||||
};
|
||||
|
||||
static const int kBitrateV2[] = {
|
||||
8, 16, 24, 32, 40, 48, 56, 64,
|
||||
80, 96, 112, 128, 144, 160
|
||||
};
|
||||
|
||||
int bitrate;
|
||||
if (version == 3 /* V1 */) {
|
||||
bitrate = (layer == 2 /* L2 */)
|
||||
? kBitrateV1L2[bitrate_index - 1]
|
||||
: kBitrateV1L3[bitrate_index - 1];
|
||||
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 1152;
|
||||
}
|
||||
} else {
|
||||
// V2 (or 2.5)
|
||||
|
||||
bitrate = kBitrateV2[bitrate_index - 1];
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 576;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_bitrate) {
|
||||
*out_bitrate = bitrate;
|
||||
}
|
||||
|
||||
if (version == 3 /* V1 */) {
|
||||
*frame_size = 144000 * bitrate / sampling_rate + padding;
|
||||
} else {
|
||||
// V2 or V2.5
|
||||
*frame_size = 72000 * bitrate / sampling_rate + padding;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_sampling_rate) {
|
||||
*out_sampling_rate = sampling_rate;
|
||||
}
|
||||
|
||||
if (out_channels) {
|
||||
int channel_mode = (header >> 6) & 3;
|
||||
|
||||
*out_channels = (channel_mode == 3) ? 1 : 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Resync(
|
||||
const sp<DataSource> &source, uint32_t match_header,
|
||||
off64_t *inout_pos, off64_t *post_id3_pos, uint32_t *out_header) {
|
||||
@@ -297,7 +146,7 @@ static bool Resync(
|
||||
|
||||
size_t frame_size;
|
||||
int sample_rate, num_channels, bitrate;
|
||||
if (!MP3Extractor::get_mp3_frame_size(
|
||||
if (!GetMPEGAudioFrameSize(
|
||||
header, &frame_size,
|
||||
&sample_rate, &num_channels, &bitrate)) {
|
||||
++pos;
|
||||
@@ -331,7 +180,7 @@ static bool Resync(
|
||||
}
|
||||
|
||||
size_t test_frame_size;
|
||||
if (!MP3Extractor::get_mp3_frame_size(
|
||||
if (!GetMPEGAudioFrameSize(
|
||||
test_header, &test_frame_size)) {
|
||||
valid = false;
|
||||
break;
|
||||
@@ -437,7 +286,7 @@ MP3Extractor::MP3Extractor(
|
||||
int sample_rate;
|
||||
int num_channels;
|
||||
int bitrate;
|
||||
get_mp3_frame_size(
|
||||
GetMPEGAudioFrameSize(
|
||||
header, &frame_size, &sample_rate, &num_channels, &bitrate);
|
||||
|
||||
mMeta = new MetaData;
|
||||
@@ -602,8 +451,9 @@ status_t MP3Source::read(
|
||||
uint32_t header = U32_AT((const uint8_t *)buffer->data());
|
||||
|
||||
if ((header & kMask) == (mFixedHeader & kMask)
|
||||
&& MP3Extractor::get_mp3_frame_size(
|
||||
header, &frame_size, &sample_rate, NULL, &bitrate, &num_samples)) {
|
||||
&& GetMPEGAudioFrameSize(
|
||||
header, &frame_size, &sample_rate, NULL,
|
||||
&bitrate, &num_samples)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const char *MEDIA_MIMETYPE_VIDEO_VPX = "video/x-vnd.on2.vp8";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_AVC = "video/avc";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_H263 = "video/3gpp";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_RAW = "video/raw";
|
||||
|
||||
const char *MEDIA_MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
|
||||
|
||||
@@ -163,7 +163,6 @@ static sp<MediaSource> InstantiateSoftwareCodec(
|
||||
|
||||
static const CodecInfo kDecoderInfo[] = {
|
||||
{ MEDIA_MIMETYPE_IMAGE_JPEG, "OMX.TI.JPEG.decode" },
|
||||
// { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.Nvidia.mp3.decoder" },
|
||||
// { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.TI.MP3.decode" },
|
||||
{ MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.google.mp3.decoder" },
|
||||
{ MEDIA_MIMETYPE_AUDIO_MPEG, "MP3Decoder" },
|
||||
@@ -207,6 +206,7 @@ static const CodecInfo kDecoderInfo[] = {
|
||||
{ MEDIA_MIMETYPE_AUDIO_VORBIS, "VorbisDecoder" },
|
||||
{ MEDIA_MIMETYPE_VIDEO_VPX, "OMX.google.vpx.decoder" },
|
||||
{ MEDIA_MIMETYPE_VIDEO_VPX, "VPXDecoder" },
|
||||
{ MEDIA_MIMETYPE_VIDEO_MPEG2, "OMX.Nvidia.mpeg2v.decode" },
|
||||
};
|
||||
|
||||
static const CodecInfo kEncoderInfo[] = {
|
||||
@@ -534,6 +534,10 @@ sp<MediaSource> OMXCodec::Create(
|
||||
err = codec->configureCodec(meta, flags);
|
||||
|
||||
if (err == OK) {
|
||||
if (!strcmp("OMX.Nvidia.mpeg2v.decode", componentName)) {
|
||||
codec->mOnlySubmitOneBufferAtOneTime = true;
|
||||
}
|
||||
|
||||
return codec;
|
||||
}
|
||||
|
||||
@@ -1357,6 +1361,8 @@ status_t OMXCodec::setVideoOutputFormat(
|
||||
compressionFormat = OMX_VIDEO_CodingH263;
|
||||
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_VPX, mime)) {
|
||||
compressionFormat = OMX_VIDEO_CodingVPX;
|
||||
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG2, mime)) {
|
||||
compressionFormat = OMX_VIDEO_CodingMPEG2;
|
||||
} else {
|
||||
LOGE("Not a supported video mime type: %s", mime);
|
||||
CHECK(!"Should not be here. Not a supported video mime type.");
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "include/VBRISeeker.h"
|
||||
|
||||
#include "include/avc_utils.h"
|
||||
#include "include/MP3Extractor.h"
|
||||
|
||||
#include <media/stagefright/foundation/ADebug.h>
|
||||
@@ -46,7 +47,7 @@ sp<VBRISeeker> VBRISeeker::CreateFromSource(
|
||||
uint32_t tmp = U32_AT(&header[0]);
|
||||
size_t frameSize;
|
||||
int sampleRate;
|
||||
if (!MP3Extractor::get_mp3_frame_size(tmp, &frameSize, &sampleRate)) {
|
||||
if (!GetMPEGAudioFrameSize(tmp, &frameSize, &sampleRate)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -376,5 +376,236 @@ sp<MetaData> MakeAACCodecSpecificData(
|
||||
return meta;
|
||||
}
|
||||
|
||||
bool ExtractDimensionsFromVOLHeader(
|
||||
const uint8_t *data, size_t size, int32_t *width, int32_t *height) {
|
||||
ABitReader br(&data[4], size - 4);
|
||||
br.skipBits(1); // random_accessible_vol
|
||||
unsigned video_object_type_indication = br.getBits(8);
|
||||
|
||||
CHECK_NE(video_object_type_indication,
|
||||
0x21u /* Fine Granularity Scalable */);
|
||||
|
||||
unsigned video_object_layer_verid;
|
||||
unsigned video_object_layer_priority;
|
||||
if (br.getBits(1)) {
|
||||
video_object_layer_verid = br.getBits(4);
|
||||
video_object_layer_priority = br.getBits(3);
|
||||
}
|
||||
unsigned aspect_ratio_info = br.getBits(4);
|
||||
if (aspect_ratio_info == 0x0f /* extended PAR */) {
|
||||
br.skipBits(8); // par_width
|
||||
br.skipBits(8); // par_height
|
||||
}
|
||||
if (br.getBits(1)) { // vol_control_parameters
|
||||
br.skipBits(2); // chroma_format
|
||||
br.skipBits(1); // low_delay
|
||||
if (br.getBits(1)) { // vbv_parameters
|
||||
br.skipBits(15); // first_half_bit_rate
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(15); // latter_half_bit_rate
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(15); // first_half_vbv_buffer_size
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(3); // latter_half_vbv_buffer_size
|
||||
br.skipBits(11); // first_half_vbv_occupancy
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(15); // latter_half_vbv_occupancy
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
}
|
||||
}
|
||||
unsigned video_object_layer_shape = br.getBits(2);
|
||||
CHECK_EQ(video_object_layer_shape, 0x00u /* rectangular */);
|
||||
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
unsigned vop_time_increment_resolution = br.getBits(16);
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
|
||||
if (br.getBits(1)) { // fixed_vop_rate
|
||||
// range [0..vop_time_increment_resolution)
|
||||
|
||||
// vop_time_increment_resolution
|
||||
// 2 => 0..1, 1 bit
|
||||
// 3 => 0..2, 2 bits
|
||||
// 4 => 0..3, 2 bits
|
||||
// 5 => 0..4, 3 bits
|
||||
// ...
|
||||
|
||||
CHECK_GT(vop_time_increment_resolution, 0u);
|
||||
--vop_time_increment_resolution;
|
||||
|
||||
unsigned numBits = 0;
|
||||
while (vop_time_increment_resolution > 0) {
|
||||
++numBits;
|
||||
vop_time_increment_resolution >>= 1;
|
||||
}
|
||||
|
||||
br.skipBits(numBits); // fixed_vop_time_increment
|
||||
}
|
||||
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
unsigned video_object_layer_width = br.getBits(13);
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
unsigned video_object_layer_height = br.getBits(13);
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
|
||||
unsigned interlaced = br.getBits(1);
|
||||
|
||||
*width = video_object_layer_width;
|
||||
*height = video_object_layer_height;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetMPEGAudioFrameSize(
|
||||
uint32_t header, size_t *frame_size,
|
||||
int *out_sampling_rate, int *out_channels,
|
||||
int *out_bitrate, int *out_num_samples) {
|
||||
*frame_size = 0;
|
||||
|
||||
if (out_sampling_rate) {
|
||||
*out_sampling_rate = 0;
|
||||
}
|
||||
|
||||
if (out_channels) {
|
||||
*out_channels = 0;
|
||||
}
|
||||
|
||||
if (out_bitrate) {
|
||||
*out_bitrate = 0;
|
||||
}
|
||||
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 1152;
|
||||
}
|
||||
|
||||
if ((header & 0xffe00000) != 0xffe00000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned version = (header >> 19) & 3;
|
||||
|
||||
if (version == 0x01) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned layer = (header >> 17) & 3;
|
||||
|
||||
if (layer == 0x00) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned protection = (header >> 16) & 1;
|
||||
|
||||
unsigned bitrate_index = (header >> 12) & 0x0f;
|
||||
|
||||
if (bitrate_index == 0 || bitrate_index == 0x0f) {
|
||||
// Disallow "free" bitrate.
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned sampling_rate_index = (header >> 10) & 3;
|
||||
|
||||
if (sampling_rate_index == 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const int kSamplingRateV1[] = { 44100, 48000, 32000 };
|
||||
int sampling_rate = kSamplingRateV1[sampling_rate_index];
|
||||
if (version == 2 /* V2 */) {
|
||||
sampling_rate /= 2;
|
||||
} else if (version == 0 /* V2.5 */) {
|
||||
sampling_rate /= 4;
|
||||
}
|
||||
|
||||
unsigned padding = (header >> 9) & 1;
|
||||
|
||||
if (layer == 3) {
|
||||
// layer I
|
||||
|
||||
static const int kBitrateV1[] = {
|
||||
32, 64, 96, 128, 160, 192, 224, 256,
|
||||
288, 320, 352, 384, 416, 448
|
||||
};
|
||||
|
||||
static const int kBitrateV2[] = {
|
||||
32, 48, 56, 64, 80, 96, 112, 128,
|
||||
144, 160, 176, 192, 224, 256
|
||||
};
|
||||
|
||||
int bitrate =
|
||||
(version == 3 /* V1 */)
|
||||
? kBitrateV1[bitrate_index - 1]
|
||||
: kBitrateV2[bitrate_index - 1];
|
||||
|
||||
if (out_bitrate) {
|
||||
*out_bitrate = bitrate;
|
||||
}
|
||||
|
||||
*frame_size = (12000 * bitrate / sampling_rate + padding) * 4;
|
||||
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 384;
|
||||
}
|
||||
} else {
|
||||
// layer II or III
|
||||
|
||||
static const int kBitrateV1L2[] = {
|
||||
32, 48, 56, 64, 80, 96, 112, 128,
|
||||
160, 192, 224, 256, 320, 384
|
||||
};
|
||||
|
||||
static const int kBitrateV1L3[] = {
|
||||
32, 40, 48, 56, 64, 80, 96, 112,
|
||||
128, 160, 192, 224, 256, 320
|
||||
};
|
||||
|
||||
static const int kBitrateV2[] = {
|
||||
8, 16, 24, 32, 40, 48, 56, 64,
|
||||
80, 96, 112, 128, 144, 160
|
||||
};
|
||||
|
||||
int bitrate;
|
||||
if (version == 3 /* V1 */) {
|
||||
bitrate = (layer == 2 /* L2 */)
|
||||
? kBitrateV1L2[bitrate_index - 1]
|
||||
: kBitrateV1L3[bitrate_index - 1];
|
||||
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 1152;
|
||||
}
|
||||
} else {
|
||||
// V2 (or 2.5)
|
||||
|
||||
bitrate = kBitrateV2[bitrate_index - 1];
|
||||
if (out_num_samples) {
|
||||
*out_num_samples = 576;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_bitrate) {
|
||||
*out_bitrate = bitrate;
|
||||
}
|
||||
|
||||
if (version == 3 /* V1 */) {
|
||||
*frame_size = 144000 * bitrate / sampling_rate + padding;
|
||||
} else {
|
||||
// V2 or V2.5
|
||||
*frame_size = 72000 * bitrate / sampling_rate + padding;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_sampling_rate) {
|
||||
*out_sampling_rate = sampling_rate;
|
||||
}
|
||||
|
||||
if (out_channels) {
|
||||
int channel_mode = (header >> 6) & 3;
|
||||
|
||||
*out_channels = (channel_mode == 3) ? 1 : 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
|
||||
@@ -223,6 +223,7 @@ void SoftMP3::onQueueFilled(OMX_U32 portIndex) {
|
||||
|
||||
if (decoderErr != NO_ENOUGH_MAIN_DATA_ERROR ||
|
||||
mConfig->outputFrameSize == 0) {
|
||||
LOGE("mp3 decoder returned error %d", decoderErr);
|
||||
|
||||
if (mConfig->outputFrameSize == 0) {
|
||||
LOGE("Output frame size is 0");
|
||||
|
||||
@@ -39,11 +39,6 @@ public:
|
||||
|
||||
virtual sp<MetaData> getMetaData();
|
||||
|
||||
static bool get_mp3_frame_size(
|
||||
uint32_t header, size_t *frame_size,
|
||||
int *out_sampling_rate = NULL, int *out_channels = NULL,
|
||||
int *out_bitrate = NULL, int *out_num_samples = NULL);
|
||||
|
||||
private:
|
||||
status_t mInitCheck;
|
||||
|
||||
|
||||
@@ -57,6 +57,16 @@ sp<MetaData> MakeAACCodecSpecificData(
|
||||
unsigned profile, unsigned sampling_freq_index,
|
||||
unsigned channel_configuration);
|
||||
|
||||
// Given an MPEG4 video VOL-header chunk (starting with 0x00 0x00 0x01 0x2?)
|
||||
// parse it and fill in dimensions, returns true iff successful.
|
||||
bool ExtractDimensionsFromVOLHeader(
|
||||
const uint8_t *data, size_t size, int32_t *width, int32_t *height);
|
||||
|
||||
bool GetMPEGAudioFrameSize(
|
||||
uint32_t header, size_t *frame_size,
|
||||
int *out_sampling_rate = NULL, int *out_channels = NULL,
|
||||
int *out_bitrate = NULL, int *out_num_samples = NULL);
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // AVC_UTILS_H_
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace android {
|
||||
static const size_t kTSPacketSize = 188;
|
||||
|
||||
struct ATSParser::Program : public RefBase {
|
||||
Program(ATSParser *parser, unsigned programMapPID);
|
||||
Program(ATSParser *parser, unsigned programNumber, unsigned programMapPID);
|
||||
|
||||
bool parsePID(
|
||||
unsigned pid, unsigned payload_unit_start_indicator,
|
||||
@@ -63,8 +63,15 @@ struct ATSParser::Program : public RefBase {
|
||||
return mFirstPTSValid;
|
||||
}
|
||||
|
||||
unsigned number() const { return mProgramNumber; }
|
||||
|
||||
void updateProgramMapPID(unsigned programMapPID) {
|
||||
mProgramMapPID = programMapPID;
|
||||
}
|
||||
|
||||
private:
|
||||
ATSParser *mParser;
|
||||
unsigned mProgramNumber;
|
||||
unsigned mProgramMapPID;
|
||||
KeyedVector<unsigned, sp<Stream> > mStreams;
|
||||
bool mFirstPTSValid;
|
||||
@@ -107,7 +114,7 @@ private:
|
||||
DiscontinuityType mPendingDiscontinuity;
|
||||
sp<AMessage> mPendingDiscontinuityExtra;
|
||||
|
||||
ElementaryStreamQueue mQueue;
|
||||
ElementaryStreamQueue *mQueue;
|
||||
|
||||
void flush();
|
||||
void parsePES(ABitReader *br);
|
||||
@@ -126,11 +133,14 @@ private:
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ATSParser::Program::Program(ATSParser *parser, unsigned programMapPID)
|
||||
ATSParser::Program::Program(
|
||||
ATSParser *parser, unsigned programNumber, unsigned programMapPID)
|
||||
: mParser(parser),
|
||||
mProgramNumber(programNumber),
|
||||
mProgramMapPID(programMapPID),
|
||||
mFirstPTSValid(false),
|
||||
mFirstPTS(0) {
|
||||
LOGV("new program number %u", programNumber);
|
||||
}
|
||||
|
||||
bool ATSParser::Program::parsePID(
|
||||
@@ -299,7 +309,7 @@ void ATSParser::Program::parseProgramMap(ABitReader *br) {
|
||||
}
|
||||
|
||||
sp<MediaSource> ATSParser::Program::getSource(SourceType type) {
|
||||
size_t index = (type == MPEG2ADTS_AUDIO) ? 0 : 0;
|
||||
size_t index = (type == AUDIO) ? 0 : 0;
|
||||
|
||||
for (size_t i = 0; i < mStreams.size(); ++i) {
|
||||
sp<MediaSource> source = mStreams.editValueAt(i)->getSource(type);
|
||||
@@ -338,14 +348,43 @@ ATSParser::Stream::Stream(
|
||||
mBuffer(new ABuffer(192 * 1024)),
|
||||
mPayloadStarted(false),
|
||||
mPendingDiscontinuity(DISCONTINUITY_NONE),
|
||||
mQueue(streamType == 0x1b
|
||||
? ElementaryStreamQueue::H264 : ElementaryStreamQueue::AAC) {
|
||||
mQueue(NULL) {
|
||||
mBuffer->setRange(0, 0);
|
||||
|
||||
switch (mStreamType) {
|
||||
case STREAMTYPE_H264:
|
||||
mQueue = new ElementaryStreamQueue(ElementaryStreamQueue::H264);
|
||||
break;
|
||||
case STREAMTYPE_MPEG2_AUDIO_ATDS:
|
||||
mQueue = new ElementaryStreamQueue(ElementaryStreamQueue::AAC);
|
||||
break;
|
||||
case STREAMTYPE_MPEG1_AUDIO:
|
||||
case STREAMTYPE_MPEG2_AUDIO:
|
||||
mQueue = new ElementaryStreamQueue(
|
||||
ElementaryStreamQueue::MPEG_AUDIO);
|
||||
break;
|
||||
|
||||
case STREAMTYPE_MPEG1_VIDEO:
|
||||
case STREAMTYPE_MPEG2_VIDEO:
|
||||
mQueue = new ElementaryStreamQueue(
|
||||
ElementaryStreamQueue::MPEG_VIDEO);
|
||||
break;
|
||||
|
||||
case STREAMTYPE_MPEG4_VIDEO:
|
||||
mQueue = new ElementaryStreamQueue(
|
||||
ElementaryStreamQueue::MPEG4_VIDEO);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LOGV("new stream PID 0x%02x, type 0x%02x", elementaryPID, streamType);
|
||||
}
|
||||
|
||||
ATSParser::Stream::~Stream() {
|
||||
delete mQueue;
|
||||
mQueue = NULL;
|
||||
}
|
||||
|
||||
void ATSParser::Stream::parse(
|
||||
@@ -397,7 +436,7 @@ void ATSParser::Stream::signalDiscontinuity(
|
||||
{
|
||||
bool isASeek = (type == DISCONTINUITY_SEEK);
|
||||
|
||||
mQueue.clear(!isASeek);
|
||||
mQueue->clear(!isASeek);
|
||||
|
||||
uint64_t resumeAtPTS;
|
||||
if (extra != NULL
|
||||
@@ -444,6 +483,12 @@ void ATSParser::Stream::parsePES(ABitReader *br) {
|
||||
|
||||
LOGV("packet_startcode_prefix = 0x%08x", packet_startcode_prefix);
|
||||
|
||||
if (packet_startcode_prefix != 1) {
|
||||
LOGV("Supposedly payload_unit_start=1 unit does not start "
|
||||
"with startcode.");
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK_EQ(packet_startcode_prefix, 0x000001u);
|
||||
|
||||
unsigned stream_id = br->getBits(8);
|
||||
@@ -611,22 +656,28 @@ void ATSParser::Stream::onPayloadData(
|
||||
const uint8_t *data, size_t size) {
|
||||
LOGV("onPayloadData mStreamType=0x%02x", mStreamType);
|
||||
|
||||
if (mQueue == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK(PTS_DTS_flags == 2 || PTS_DTS_flags == 3);
|
||||
int64_t timeUs = mProgram->convertPTSToTimestamp(PTS);
|
||||
|
||||
status_t err = mQueue.appendData(data, size, timeUs);
|
||||
status_t err = mQueue->appendData(data, size, timeUs);
|
||||
|
||||
if (err != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
sp<ABuffer> accessUnit;
|
||||
while ((accessUnit = mQueue.dequeueAccessUnit()) != NULL) {
|
||||
while ((accessUnit = mQueue->dequeueAccessUnit()) != NULL) {
|
||||
if (mSource == NULL) {
|
||||
sp<MetaData> meta = mQueue.getFormat();
|
||||
sp<MetaData> meta = mQueue->getFormat();
|
||||
|
||||
if (meta != NULL) {
|
||||
LOGV("created source!");
|
||||
LOGV("Stream PID 0x%08x of type 0x%02x now has data.",
|
||||
mElementaryPID, mStreamType);
|
||||
|
||||
mSource = new AnotherPacketSource(meta);
|
||||
|
||||
if (mPendingDiscontinuity != DISCONTINUITY_NONE) {
|
||||
@@ -638,13 +689,13 @@ void ATSParser::Stream::onPayloadData(
|
||||
|
||||
mSource->queueAccessUnit(accessUnit);
|
||||
}
|
||||
} else if (mQueue.getFormat() != NULL) {
|
||||
} else if (mQueue->getFormat() != NULL) {
|
||||
// After a discontinuity we invalidate the queue's format
|
||||
// and won't enqueue any access units to the source until
|
||||
// the queue has reestablished the new format.
|
||||
|
||||
if (mSource->getFormat() == NULL) {
|
||||
mSource->setFormat(mQueue.getFormat());
|
||||
mSource->setFormat(mQueue->getFormat());
|
||||
}
|
||||
mSource->queueAccessUnit(accessUnit);
|
||||
}
|
||||
@@ -652,9 +703,30 @@ void ATSParser::Stream::onPayloadData(
|
||||
}
|
||||
|
||||
sp<MediaSource> ATSParser::Stream::getSource(SourceType type) {
|
||||
if ((type == AVC_VIDEO && mStreamType == 0x1b)
|
||||
|| (type == MPEG2ADTS_AUDIO && mStreamType == 0x0f)) {
|
||||
return mSource;
|
||||
switch (type) {
|
||||
case VIDEO:
|
||||
{
|
||||
if (mStreamType == STREAMTYPE_H264
|
||||
|| mStreamType == STREAMTYPE_MPEG1_VIDEO
|
||||
|| mStreamType == STREAMTYPE_MPEG2_VIDEO
|
||||
|| mStreamType == STREAMTYPE_MPEG4_VIDEO) {
|
||||
return mSource;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDIO:
|
||||
{
|
||||
if (mStreamType == STREAMTYPE_MPEG1_AUDIO
|
||||
|| mStreamType == STREAMTYPE_MPEG2_AUDIO
|
||||
|| mStreamType == STREAMTYPE_MPEG2_AUDIO_ATDS) {
|
||||
return mSource;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -729,7 +801,21 @@ void ATSParser::parseProgramAssociationTable(ABitReader *br) {
|
||||
|
||||
LOGV(" program_map_PID = 0x%04x", programMapPID);
|
||||
|
||||
mPrograms.push(new Program(this, programMapPID));
|
||||
bool found = false;
|
||||
for (size_t index = 0; index < mPrograms.size(); ++index) {
|
||||
const sp<Program> &program = mPrograms.itemAt(index);
|
||||
|
||||
if (program->number() == program_number) {
|
||||
program->updateProgramMapPID(programMapPID);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
mPrograms.push(
|
||||
new Program(this, program_number, programMapPID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,8 +891,16 @@ void ATSParser::parseTS(ABitReader *br) {
|
||||
}
|
||||
|
||||
sp<MediaSource> ATSParser::getSource(SourceType type) {
|
||||
int which = -1; // any
|
||||
|
||||
for (size_t i = 0; i < mPrograms.size(); ++i) {
|
||||
sp<MediaSource> source = mPrograms.editItemAt(i)->getSource(type);
|
||||
const sp<Program> &program = mPrograms.editItemAt(i);
|
||||
|
||||
if (which >= 0 && (int)program->number() != which) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sp<MediaSource> source = program->getSource(type);
|
||||
|
||||
if (source != NULL) {
|
||||
return source;
|
||||
|
||||
@@ -48,8 +48,8 @@ struct ATSParser : public RefBase {
|
||||
void signalEOS(status_t finalResult);
|
||||
|
||||
enum SourceType {
|
||||
AVC_VIDEO,
|
||||
MPEG2ADTS_AUDIO
|
||||
VIDEO,
|
||||
AUDIO
|
||||
};
|
||||
sp<MediaSource> getSource(SourceType type);
|
||||
|
||||
@@ -59,6 +59,17 @@ protected:
|
||||
virtual ~ATSParser();
|
||||
|
||||
private:
|
||||
enum {
|
||||
// From ISO/IEC 13818-1: 2000 (E), Table 2-29
|
||||
STREAMTYPE_MPEG1_VIDEO = 0x01,
|
||||
STREAMTYPE_MPEG2_VIDEO = 0x02,
|
||||
STREAMTYPE_MPEG1_AUDIO = 0x03,
|
||||
STREAMTYPE_MPEG2_AUDIO = 0x04,
|
||||
STREAMTYPE_MPEG2_AUDIO_ATDS = 0x0f,
|
||||
STREAMTYPE_MPEG4_VIDEO = 0x10,
|
||||
STREAMTYPE_H264 = 0x1b,
|
||||
};
|
||||
|
||||
struct Program;
|
||||
struct Stream;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <media/stagefright/MediaErrors.h>
|
||||
#include <media/stagefright/MediaDefs.h>
|
||||
#include <media/stagefright/MetaData.h>
|
||||
#include <media/stagefright/Utils.h>
|
||||
|
||||
#include "include/avc_utils.h"
|
||||
|
||||
@@ -79,11 +80,49 @@ static bool IsSeeminglyValidADTSHeader(const uint8_t *ptr, size_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsSeeminglyValidMPEGAudioHeader(const uint8_t *ptr, size_t size) {
|
||||
if (size < 3) {
|
||||
// Not enough data to verify header.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptr[0] != 0xff || (ptr[1] >> 5) != 0x07) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned ID = (ptr[1] >> 3) & 3;
|
||||
|
||||
if (ID == 1) {
|
||||
return false; // reserved
|
||||
}
|
||||
|
||||
unsigned layer = (ptr[1] >> 1) & 3;
|
||||
|
||||
if (layer == 0) {
|
||||
return false; // reserved
|
||||
}
|
||||
|
||||
unsigned bitrateIndex = (ptr[2] >> 4);
|
||||
|
||||
if (bitrateIndex == 0x0f) {
|
||||
return false; // reserved
|
||||
}
|
||||
|
||||
unsigned samplingRateIndex = (ptr[2] >> 2) & 3;
|
||||
|
||||
if (samplingRateIndex == 3) {
|
||||
return false; // reserved
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
status_t ElementaryStreamQueue::appendData(
|
||||
const void *data, size_t size, int64_t timeUs) {
|
||||
if (mBuffer == NULL || mBuffer->size() == 0) {
|
||||
switch (mMode) {
|
||||
case H264:
|
||||
case MPEG_VIDEO:
|
||||
{
|
||||
#if 0
|
||||
if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) {
|
||||
@@ -105,7 +144,40 @@ status_t ElementaryStreamQueue::appendData(
|
||||
}
|
||||
|
||||
if (startOffset > 0) {
|
||||
LOGI("found something resembling an H.264 syncword at "
|
||||
LOGI("found something resembling an H.264/MPEG syncword at "
|
||||
"offset %ld",
|
||||
startOffset);
|
||||
}
|
||||
|
||||
data = &ptr[startOffset];
|
||||
size -= startOffset;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case MPEG4_VIDEO:
|
||||
{
|
||||
#if 0
|
||||
if (size < 3 || memcmp("\x00\x00\x01", data, 3)) {
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
#else
|
||||
uint8_t *ptr = (uint8_t *)data;
|
||||
|
||||
ssize_t startOffset = -1;
|
||||
for (size_t i = 0; i + 2 < size; ++i) {
|
||||
if (!memcmp("\x00\x00\x01", &ptr[i], 3)) {
|
||||
startOffset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startOffset < 0) {
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
if (startOffset > 0) {
|
||||
LOGI("found something resembling an H.264/MPEG syncword at "
|
||||
"offset %ld",
|
||||
startOffset);
|
||||
}
|
||||
@@ -148,6 +220,33 @@ status_t ElementaryStreamQueue::appendData(
|
||||
break;
|
||||
}
|
||||
|
||||
case MPEG_AUDIO:
|
||||
{
|
||||
uint8_t *ptr = (uint8_t *)data;
|
||||
|
||||
ssize_t startOffset = -1;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
if (IsSeeminglyValidMPEGAudioHeader(&ptr[i], size - i)) {
|
||||
startOffset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startOffset < 0) {
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
if (startOffset > 0) {
|
||||
LOGI("found something resembling an MPEG audio "
|
||||
"syncword at offset %ld",
|
||||
startOffset);
|
||||
}
|
||||
|
||||
data = &ptr[startOffset];
|
||||
size -= startOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
TRESPASS();
|
||||
break;
|
||||
@@ -190,11 +289,18 @@ status_t ElementaryStreamQueue::appendData(
|
||||
}
|
||||
|
||||
sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() {
|
||||
if (mMode == H264) {
|
||||
return dequeueAccessUnitH264();
|
||||
} else {
|
||||
CHECK_EQ((unsigned)mMode, (unsigned)AAC);
|
||||
return dequeueAccessUnitAAC();
|
||||
switch (mMode) {
|
||||
case H264:
|
||||
return dequeueAccessUnitH264();
|
||||
case AAC:
|
||||
return dequeueAccessUnitAAC();
|
||||
case MPEG_VIDEO:
|
||||
return dequeueAccessUnitMPEGVideo();
|
||||
case MPEG4_VIDEO:
|
||||
return dequeueAccessUnitMPEG4Video();
|
||||
default:
|
||||
CHECK_EQ((unsigned)mMode, (unsigned)MPEG_AUDIO);
|
||||
return dequeueAccessUnitMPEGAudio();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,4 +561,371 @@ sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitH264() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitMPEGAudio() {
|
||||
const uint8_t *data = mBuffer->data();
|
||||
size_t size = mBuffer->size();
|
||||
|
||||
if (size < 4) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t header = U32_AT(data);
|
||||
|
||||
size_t frameSize;
|
||||
int samplingRate, numChannels, bitrate, numSamples;
|
||||
CHECK(GetMPEGAudioFrameSize(
|
||||
header, &frameSize, &samplingRate, &numChannels,
|
||||
&bitrate, &numSamples));
|
||||
|
||||
if (size < frameSize) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sp<ABuffer> accessUnit = new ABuffer(frameSize);
|
||||
memcpy(accessUnit->data(), data, frameSize);
|
||||
|
||||
memmove(mBuffer->data(),
|
||||
mBuffer->data() + frameSize,
|
||||
mBuffer->size() - frameSize);
|
||||
|
||||
mBuffer->setRange(0, mBuffer->size() - frameSize);
|
||||
|
||||
int64_t timeUs = fetchTimestamp(frameSize);
|
||||
CHECK_GE(timeUs, 0ll);
|
||||
|
||||
accessUnit->meta()->setInt64("timeUs", timeUs);
|
||||
|
||||
if (mFormat == NULL) {
|
||||
mFormat = new MetaData;
|
||||
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
|
||||
mFormat->setInt32(kKeySampleRate, samplingRate);
|
||||
mFormat->setInt32(kKeyChannelCount, numChannels);
|
||||
}
|
||||
|
||||
return accessUnit;
|
||||
}
|
||||
|
||||
static void EncodeSize14(uint8_t **_ptr, size_t size) {
|
||||
CHECK_LE(size, 0x3fff);
|
||||
|
||||
uint8_t *ptr = *_ptr;
|
||||
|
||||
*ptr++ = 0x80 | (size >> 7);
|
||||
*ptr++ = size & 0x7f;
|
||||
|
||||
*_ptr = ptr;
|
||||
}
|
||||
|
||||
static sp<ABuffer> MakeMPEGVideoESDS(const sp<ABuffer> &csd) {
|
||||
sp<ABuffer> esds = new ABuffer(csd->size() + 25);
|
||||
|
||||
uint8_t *ptr = esds->data();
|
||||
*ptr++ = 0x03;
|
||||
EncodeSize14(&ptr, 22 + csd->size());
|
||||
|
||||
*ptr++ = 0x00; // ES_ID
|
||||
*ptr++ = 0x00;
|
||||
|
||||
*ptr++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag
|
||||
|
||||
*ptr++ = 0x04;
|
||||
EncodeSize14(&ptr, 16 + csd->size());
|
||||
|
||||
*ptr++ = 0x40; // Audio ISO/IEC 14496-3
|
||||
|
||||
for (size_t i = 0; i < 12; ++i) {
|
||||
*ptr++ = 0x00;
|
||||
}
|
||||
|
||||
*ptr++ = 0x05;
|
||||
EncodeSize14(&ptr, csd->size());
|
||||
|
||||
memcpy(ptr, csd->data(), csd->size());
|
||||
|
||||
return esds;
|
||||
}
|
||||
|
||||
sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitMPEGVideo() {
|
||||
const uint8_t *data = mBuffer->data();
|
||||
size_t size = mBuffer->size();
|
||||
|
||||
bool sawPictureStart = false;
|
||||
int pprevStartCode = -1;
|
||||
int prevStartCode = -1;
|
||||
int currentStartCode = -1;
|
||||
|
||||
size_t offset = 0;
|
||||
while (offset + 3 < size) {
|
||||
if (memcmp(&data[offset], "\x00\x00\x01", 3)) {
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
pprevStartCode = prevStartCode;
|
||||
prevStartCode = currentStartCode;
|
||||
currentStartCode = data[offset + 3];
|
||||
|
||||
if (currentStartCode == 0xb3 && mFormat == NULL) {
|
||||
memmove(mBuffer->data(), mBuffer->data() + offset, size - offset);
|
||||
size -= offset;
|
||||
(void)fetchTimestamp(offset);
|
||||
offset = 0;
|
||||
mBuffer->setRange(0, size);
|
||||
}
|
||||
|
||||
if ((prevStartCode == 0xb3 && currentStartCode != 0xb5)
|
||||
|| (pprevStartCode == 0xb3 && prevStartCode == 0xb5)) {
|
||||
// seqHeader without/with extension
|
||||
|
||||
if (mFormat == NULL) {
|
||||
CHECK_GE(size, 7u);
|
||||
|
||||
unsigned width =
|
||||
(data[4] << 4) | data[5] >> 4;
|
||||
|
||||
unsigned height =
|
||||
((data[5] & 0x0f) << 8) | data[6];
|
||||
|
||||
mFormat = new MetaData;
|
||||
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG2);
|
||||
mFormat->setInt32(kKeyWidth, width);
|
||||
mFormat->setInt32(kKeyHeight, height);
|
||||
|
||||
LOGI("found MPEG2 video codec config (%d x %d)", width, height);
|
||||
|
||||
sp<ABuffer> csd = new ABuffer(offset);
|
||||
memcpy(csd->data(), data, offset);
|
||||
|
||||
memmove(mBuffer->data(),
|
||||
mBuffer->data() + offset,
|
||||
mBuffer->size() - offset);
|
||||
|
||||
mBuffer->setRange(0, mBuffer->size() - offset);
|
||||
size -= offset;
|
||||
(void)fetchTimestamp(offset);
|
||||
offset = 0;
|
||||
|
||||
// hexdump(csd->data(), csd->size());
|
||||
|
||||
sp<ABuffer> esds = MakeMPEGVideoESDS(csd);
|
||||
mFormat->setData(
|
||||
kKeyESDS, kTypeESDS, esds->data(), esds->size());
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (mFormat != NULL && currentStartCode == 0x00) {
|
||||
// Picture start
|
||||
|
||||
if (!sawPictureStart) {
|
||||
sawPictureStart = true;
|
||||
} else {
|
||||
sp<ABuffer> accessUnit = new ABuffer(offset);
|
||||
memcpy(accessUnit->data(), data, offset);
|
||||
|
||||
memmove(mBuffer->data(),
|
||||
mBuffer->data() + offset,
|
||||
mBuffer->size() - offset);
|
||||
|
||||
mBuffer->setRange(0, mBuffer->size() - offset);
|
||||
|
||||
int64_t timeUs = fetchTimestamp(offset);
|
||||
CHECK_GE(timeUs, 0ll);
|
||||
|
||||
offset = 0;
|
||||
|
||||
accessUnit->meta()->setInt64("timeUs", timeUs);
|
||||
|
||||
LOGV("returning MPEG video access unit at time %lld us",
|
||||
timeUs);
|
||||
|
||||
// hexdump(accessUnit->data(), accessUnit->size());
|
||||
|
||||
return accessUnit;
|
||||
}
|
||||
}
|
||||
|
||||
++offset;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t getNextChunkSize(
|
||||
const uint8_t *data, size_t size) {
|
||||
static const char kStartCode[] = "\x00\x00\x01";
|
||||
|
||||
if (size < 3) {
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
if (memcmp(kStartCode, data, 3)) {
|
||||
TRESPASS();
|
||||
}
|
||||
|
||||
size_t offset = 3;
|
||||
while (offset + 2 < size) {
|
||||
if (!memcmp(&data[offset], kStartCode, 3)) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
++offset;
|
||||
}
|
||||
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitMPEG4Video() {
|
||||
uint8_t *data = mBuffer->data();
|
||||
size_t size = mBuffer->size();
|
||||
|
||||
enum {
|
||||
SKIP_TO_VISUAL_OBJECT_SEQ_START,
|
||||
EXPECT_VISUAL_OBJECT_START,
|
||||
EXPECT_VO_START,
|
||||
EXPECT_VOL_START,
|
||||
WAIT_FOR_VOP_START,
|
||||
SKIP_TO_VOP_START,
|
||||
|
||||
} state;
|
||||
|
||||
if (mFormat == NULL) {
|
||||
state = SKIP_TO_VISUAL_OBJECT_SEQ_START;
|
||||
} else {
|
||||
state = SKIP_TO_VOP_START;
|
||||
}
|
||||
|
||||
int32_t width = -1, height = -1;
|
||||
|
||||
size_t offset = 0;
|
||||
ssize_t chunkSize;
|
||||
while ((chunkSize = getNextChunkSize(
|
||||
&data[offset], size - offset)) > 0) {
|
||||
bool discard = false;
|
||||
|
||||
unsigned chunkType = data[offset + 3];
|
||||
|
||||
switch (state) {
|
||||
case SKIP_TO_VISUAL_OBJECT_SEQ_START:
|
||||
{
|
||||
if (chunkType == 0xb0) {
|
||||
// Discard anything before this marker.
|
||||
|
||||
state = EXPECT_VISUAL_OBJECT_START;
|
||||
} else {
|
||||
discard = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EXPECT_VISUAL_OBJECT_START:
|
||||
{
|
||||
CHECK_EQ(chunkType, 0xb5);
|
||||
state = EXPECT_VO_START;
|
||||
break;
|
||||
}
|
||||
|
||||
case EXPECT_VO_START:
|
||||
{
|
||||
CHECK_LE(chunkType, 0x1f);
|
||||
state = EXPECT_VOL_START;
|
||||
break;
|
||||
}
|
||||
|
||||
case EXPECT_VOL_START:
|
||||
{
|
||||
CHECK((chunkType & 0xf0) == 0x20);
|
||||
|
||||
CHECK(ExtractDimensionsFromVOLHeader(
|
||||
&data[offset], chunkSize,
|
||||
&width, &height));
|
||||
|
||||
state = WAIT_FOR_VOP_START;
|
||||
break;
|
||||
}
|
||||
|
||||
case WAIT_FOR_VOP_START:
|
||||
{
|
||||
if (chunkType == 0xb3 || chunkType == 0xb6) {
|
||||
// group of VOP or VOP start.
|
||||
|
||||
mFormat = new MetaData;
|
||||
mFormat->setCString(
|
||||
kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
|
||||
|
||||
mFormat->setInt32(kKeyWidth, width);
|
||||
mFormat->setInt32(kKeyHeight, height);
|
||||
|
||||
LOGI("found MPEG4 video codec config (%d x %d)",
|
||||
width, height);
|
||||
|
||||
sp<ABuffer> csd = new ABuffer(offset);
|
||||
memcpy(csd->data(), data, offset);
|
||||
|
||||
// hexdump(csd->data(), csd->size());
|
||||
|
||||
sp<ABuffer> esds = MakeMPEGVideoESDS(csd);
|
||||
mFormat->setData(
|
||||
kKeyESDS, kTypeESDS,
|
||||
esds->data(), esds->size());
|
||||
|
||||
discard = true;
|
||||
state = SKIP_TO_VOP_START;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SKIP_TO_VOP_START:
|
||||
{
|
||||
if (chunkType == 0xb6) {
|
||||
offset += chunkSize;
|
||||
|
||||
sp<ABuffer> accessUnit = new ABuffer(offset);
|
||||
memcpy(accessUnit->data(), data, offset);
|
||||
|
||||
memmove(data, &data[offset], size - offset);
|
||||
size -= offset;
|
||||
mBuffer->setRange(0, size);
|
||||
|
||||
int64_t timeUs = fetchTimestamp(offset);
|
||||
CHECK_GE(timeUs, 0ll);
|
||||
|
||||
offset = 0;
|
||||
|
||||
accessUnit->meta()->setInt64("timeUs", timeUs);
|
||||
|
||||
LOGV("returning MPEG4 video access unit at time %lld us",
|
||||
timeUs);
|
||||
|
||||
// hexdump(accessUnit->data(), accessUnit->size());
|
||||
|
||||
return accessUnit;
|
||||
} else if (chunkType != 0xb3) {
|
||||
offset += chunkSize;
|
||||
discard = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
TRESPASS();
|
||||
}
|
||||
|
||||
if (discard) {
|
||||
(void)fetchTimestamp(offset);
|
||||
memmove(data, &data[offset], size - offset);
|
||||
size -= offset;
|
||||
offset = 0;
|
||||
mBuffer->setRange(0, size);
|
||||
} else {
|
||||
offset += chunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
@@ -31,7 +31,10 @@ struct MetaData;
|
||||
struct ElementaryStreamQueue {
|
||||
enum Mode {
|
||||
H264,
|
||||
AAC
|
||||
AAC,
|
||||
MPEG_AUDIO,
|
||||
MPEG_VIDEO,
|
||||
MPEG4_VIDEO,
|
||||
};
|
||||
ElementaryStreamQueue(Mode mode);
|
||||
|
||||
@@ -57,6 +60,9 @@ private:
|
||||
|
||||
sp<ABuffer> dequeueAccessUnitH264();
|
||||
sp<ABuffer> dequeueAccessUnitAAC();
|
||||
sp<ABuffer> dequeueAccessUnitMPEGAudio();
|
||||
sp<ABuffer> dequeueAccessUnitMPEGVideo();
|
||||
sp<ABuffer> dequeueAccessUnitMPEG4Video();
|
||||
|
||||
// consume a logical (compressed) access unit of size "size",
|
||||
// returns its timestamp in us (or -1 if no time information).
|
||||
|
||||
@@ -175,7 +175,7 @@ void MPEG2TSExtractor::init() {
|
||||
if (!haveVideo) {
|
||||
sp<AnotherPacketSource> impl =
|
||||
(AnotherPacketSource *)mParser->getSource(
|
||||
ATSParser::AVC_VIDEO).get();
|
||||
ATSParser::VIDEO).get();
|
||||
|
||||
if (impl != NULL) {
|
||||
haveVideo = true;
|
||||
@@ -186,7 +186,7 @@ void MPEG2TSExtractor::init() {
|
||||
if (!haveAudio) {
|
||||
sp<AnotherPacketSource> impl =
|
||||
(AnotherPacketSource *)mParser->getSource(
|
||||
ATSParser::MPEG2ADTS_AUDIO).get();
|
||||
ATSParser::AUDIO).get();
|
||||
|
||||
if (impl != NULL) {
|
||||
haveAudio = true;
|
||||
@@ -194,7 +194,7 @@ void MPEG2TSExtractor::init() {
|
||||
}
|
||||
}
|
||||
|
||||
if (++numPacketsParsed > 2500) {
|
||||
if (++numPacketsParsed > 10000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ static uint8_t *EncodeSize(uint8_t *dst, size_t x) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
static bool ExtractDimensionsFromVOLHeader(
|
||||
static bool ExtractDimensionsMPEG4Config(
|
||||
const sp<ABuffer> &config, int32_t *width, int32_t *height) {
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
@@ -352,87 +352,11 @@ static bool ExtractDimensionsFromVOLHeader(
|
||||
return false;
|
||||
}
|
||||
|
||||
ABitReader br(&ptr[offset + 4], config->size() - offset - 4);
|
||||
br.skipBits(1); // random_accessible_vol
|
||||
unsigned video_object_type_indication = br.getBits(8);
|
||||
|
||||
CHECK_NE(video_object_type_indication,
|
||||
0x21u /* Fine Granularity Scalable */);
|
||||
|
||||
unsigned video_object_layer_verid;
|
||||
unsigned video_object_layer_priority;
|
||||
if (br.getBits(1)) {
|
||||
video_object_layer_verid = br.getBits(4);
|
||||
video_object_layer_priority = br.getBits(3);
|
||||
}
|
||||
unsigned aspect_ratio_info = br.getBits(4);
|
||||
if (aspect_ratio_info == 0x0f /* extended PAR */) {
|
||||
br.skipBits(8); // par_width
|
||||
br.skipBits(8); // par_height
|
||||
}
|
||||
if (br.getBits(1)) { // vol_control_parameters
|
||||
br.skipBits(2); // chroma_format
|
||||
br.skipBits(1); // low_delay
|
||||
if (br.getBits(1)) { // vbv_parameters
|
||||
br.skipBits(15); // first_half_bit_rate
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(15); // latter_half_bit_rate
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(15); // first_half_vbv_buffer_size
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(3); // latter_half_vbv_buffer_size
|
||||
br.skipBits(11); // first_half_vbv_occupancy
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
br.skipBits(15); // latter_half_vbv_occupancy
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
}
|
||||
}
|
||||
unsigned video_object_layer_shape = br.getBits(2);
|
||||
CHECK_EQ(video_object_layer_shape, 0x00u /* rectangular */);
|
||||
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
unsigned vop_time_increment_resolution = br.getBits(16);
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
|
||||
if (br.getBits(1)) { // fixed_vop_rate
|
||||
// range [0..vop_time_increment_resolution)
|
||||
|
||||
// vop_time_increment_resolution
|
||||
// 2 => 0..1, 1 bit
|
||||
// 3 => 0..2, 2 bits
|
||||
// 4 => 0..3, 2 bits
|
||||
// 5 => 0..4, 3 bits
|
||||
// ...
|
||||
|
||||
CHECK_GT(vop_time_increment_resolution, 0u);
|
||||
--vop_time_increment_resolution;
|
||||
|
||||
unsigned numBits = 0;
|
||||
while (vop_time_increment_resolution > 0) {
|
||||
++numBits;
|
||||
vop_time_increment_resolution >>= 1;
|
||||
}
|
||||
|
||||
br.skipBits(numBits); // fixed_vop_time_increment
|
||||
}
|
||||
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
unsigned video_object_layer_width = br.getBits(13);
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
unsigned video_object_layer_height = br.getBits(13);
|
||||
CHECK(br.getBits(1)); // marker_bit
|
||||
|
||||
unsigned interlaced = br.getBits(1);
|
||||
|
||||
*width = video_object_layer_width;
|
||||
*height = video_object_layer_height;
|
||||
|
||||
LOGI("VOL dimensions = %dx%d", *width, *height);
|
||||
|
||||
return true;
|
||||
return ExtractDimensionsFromVOLHeader(
|
||||
&ptr[offset], config->size() - offset, width, height);
|
||||
}
|
||||
|
||||
sp<ABuffer> MakeMPEG4VideoCodecSpecificData(
|
||||
static sp<ABuffer> MakeMPEG4VideoCodecSpecificData(
|
||||
const char *params, int32_t *width, int32_t *height) {
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
@@ -443,10 +367,12 @@ sp<ABuffer> MakeMPEG4VideoCodecSpecificData(
|
||||
sp<ABuffer> config = decodeHex(val);
|
||||
CHECK(config != NULL);
|
||||
|
||||
if (!ExtractDimensionsFromVOLHeader(config, width, height)) {
|
||||
if (!ExtractDimensionsMPEG4Config(config, width, height)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOGI("VOL dimensions = %dx%d", *width, *height);
|
||||
|
||||
size_t len1 = config->size() + GetSizeWidth(config->size()) + 1;
|
||||
size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13;
|
||||
size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3;
|
||||
|
||||
Reference in New Issue
Block a user