From c68a48c474f609df3eeb7d9738675d6ac8835e0a Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 8 Oct 2010 12:06:27 -0700 Subject: Refactor some more h.264 utility code out into avc_utils. Work around a hardware decoder issue by making sure the first access unit submitted to a decoder at startup or after seek is an IDR. Change-Id: I61936601e55df7e4c23a8c13087579a4f85bd6e6 --- media/libstagefright/avc_utils.cpp | 174 ++++++++++++++++++++++++++++ media/libstagefright/include/avc_utils.h | 10 ++ media/libstagefright/mpeg2ts/ESQueue.cpp | 150 ------------------------ media/libstagefright/mpeg2ts/ESQueue.h | 3 - media/libstagefright/rtsp/APacketSource.cpp | 20 ++++ media/libstagefright/rtsp/APacketSource.h | 3 + 6 files changed, 207 insertions(+), 153 deletions(-) (limited to 'media') diff --git a/media/libstagefright/avc_utils.cpp b/media/libstagefright/avc_utils.cpp index 8762d29..478e40c 100644 --- a/media/libstagefright/avc_utils.cpp +++ b/media/libstagefright/avc_utils.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include +#include namespace android { @@ -124,5 +127,176 @@ void FindAVCDimensions( } } +status_t getNextNALUnit( + const uint8_t **_data, size_t *_size, + const uint8_t **nalStart, size_t *nalSize, + bool startCodeFollows) { + const uint8_t *data = *_data; + size_t size = *_size; + + *nalStart = NULL; + *nalSize = 0; + + if (size == 0) { + return -EAGAIN; + } + + // Skip any number of leading 0x00. + + size_t offset = 0; + while (offset < size && data[offset] == 0x00) { + ++offset; + } + + if (offset == size) { + return -EAGAIN; + } + + // A valid startcode consists of at least two 0x00 bytes followed by 0x01. + + if (offset < 2 || data[offset] != 0x01) { + return ERROR_MALFORMED; + } + + ++offset; + + size_t startOffset = offset; + + for (;;) { + while (offset < size && data[offset] != 0x01) { + ++offset; + } + + if (offset == size) { + if (startCodeFollows) { + offset = size + 2; + break; + } + + return -EAGAIN; + } + + if (data[offset - 1] == 0x00 && data[offset - 2] == 0x00) { + break; + } + + ++offset; + } + + size_t endOffset = offset - 2; + while (data[endOffset - 1] == 0x00) { + --endOffset; + } + + *nalStart = &data[startOffset]; + *nalSize = endOffset - startOffset; + + if (offset + 2 < size) { + *_data = &data[offset - 2]; + *_size = size - offset + 2; + } else { + *_data = NULL; + *_size = 0; + } + + return OK; +} + +static sp FindNAL( + const uint8_t *data, size_t size, unsigned nalType, + size_t *stopOffset) { + const uint8_t *nalStart; + size_t nalSize; + while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) { + if ((nalStart[0] & 0x1f) == nalType) { + sp buffer = new ABuffer(nalSize); + memcpy(buffer->data(), nalStart, nalSize); + return buffer; + } + } + + return NULL; +} + +sp MakeAVCCodecSpecificData(const sp &accessUnit) { + const uint8_t *data = accessUnit->data(); + size_t size = accessUnit->size(); + + sp seqParamSet = FindNAL(data, size, 7, NULL); + if (seqParamSet == NULL) { + return NULL; + } + + int32_t width, height; + FindAVCDimensions(seqParamSet, &width, &height); + + size_t stopOffset; + sp picParamSet = FindNAL(data, size, 8, &stopOffset); + CHECK(picParamSet != NULL); + + size_t csdSize = + 1 + 3 + 1 + 1 + + 2 * 1 + seqParamSet->size() + + 1 + 2 * 1 + picParamSet->size(); + + sp csd = new ABuffer(csdSize); + uint8_t *out = csd->data(); + + *out++ = 0x01; // configurationVersion + memcpy(out, seqParamSet->data() + 1, 3); // profile/level... + out += 3; + *out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes + *out++ = 0xe0 | 1; + + *out++ = seqParamSet->size() >> 8; + *out++ = seqParamSet->size() & 0xff; + memcpy(out, seqParamSet->data(), seqParamSet->size()); + out += seqParamSet->size(); + + *out++ = 1; + + *out++ = picParamSet->size() >> 8; + *out++ = picParamSet->size() & 0xff; + memcpy(out, picParamSet->data(), picParamSet->size()); + +#if 0 + LOGI("AVC seq param set"); + hexdump(seqParamSet->data(), seqParamSet->size()); +#endif + + sp meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + + meta->setData(kKeyAVCC, 0, csd->data(), csd->size()); + meta->setInt32(kKeyWidth, width); + meta->setInt32(kKeyHeight, height); + + LOGI("found AVC codec config (%d x %d)", width, height); + + return meta; +} + +bool IsIDR(const sp &buffer) { + const uint8_t *data = buffer->data(); + size_t size = buffer->size(); + + bool foundIDR = false; + + const uint8_t *nalStart; + size_t nalSize; + while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) { + CHECK_GT(nalSize, 0u); + + unsigned nalType = nalStart[0] & 0x1f; + + if (nalType == 5) { + foundIDR = true; + break; + } + } + + return foundIDR; +} + } // namespace android diff --git a/media/libstagefright/include/avc_utils.h b/media/libstagefright/include/avc_utils.h index 6602852..62cfc36 100644 --- a/media/libstagefright/include/avc_utils.h +++ b/media/libstagefright/include/avc_utils.h @@ -29,6 +29,16 @@ void FindAVCDimensions( unsigned parseUE(ABitReader *br); +status_t getNextNALUnit( + const uint8_t **_data, size_t *_size, + const uint8_t **nalStart, size_t *nalSize, + bool startCodeFollows = false); + +struct MetaData; +sp MakeAVCCodecSpecificData(const sp &accessUnit); + +bool IsIDR(const sp &accessUnit); + } // namespace android #endif // AVC_UTILS_H_ diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp index 4a75ee4..a13287e 100644 --- a/media/libstagefright/mpeg2ts/ESQueue.cpp +++ b/media/libstagefright/mpeg2ts/ESQueue.cpp @@ -40,81 +40,6 @@ sp ElementaryStreamQueue::getFormat() { return mFormat; } -static status_t getNextNALUnit( - const uint8_t **_data, size_t *_size, - const uint8_t **nalStart, size_t *nalSize, - bool startCodeFollows = false) { - const uint8_t *data = *_data; - size_t size = *_size; - - *nalStart = NULL; - *nalSize = 0; - - if (size == 0) { - return -EAGAIN; - } - - // Skip any number of leading 0x00. - - size_t offset = 0; - while (offset < size && data[offset] == 0x00) { - ++offset; - } - - if (offset == size) { - return -EAGAIN; - } - - // A valid startcode consists of at least two 0x00 bytes followed by 0x01. - - if (offset < 2 || data[offset] != 0x01) { - return ERROR_MALFORMED; - } - - ++offset; - - size_t startOffset = offset; - - for (;;) { - while (offset < size && data[offset] != 0x01) { - ++offset; - } - - if (offset == size) { - if (startCodeFollows) { - offset = size + 2; - break; - } - - return -EAGAIN; - } - - if (data[offset - 1] == 0x00 && data[offset - 2] == 0x00) { - break; - } - - ++offset; - } - - size_t endOffset = offset - 2; - while (data[endOffset - 1] == 0x00) { - --endOffset; - } - - *nalStart = &data[startOffset]; - *nalSize = endOffset - startOffset; - - if (offset + 2 < size) { - *_data = &data[offset - 2]; - *_size = size - offset + 2; - } else { - *_data = NULL; - *_size = 0; - } - - return OK; -} - void ElementaryStreamQueue::clear() { mBuffer->setRange(0, 0); mFormat.clear(); @@ -433,79 +358,4 @@ sp ElementaryStreamQueue::dequeueAccessUnitH264() { return NULL; } -static sp FindNAL( - const uint8_t *data, size_t size, unsigned nalType, - size_t *stopOffset) { - const uint8_t *nalStart; - size_t nalSize; - while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) { - if ((nalStart[0] & 0x1f) == nalType) { - sp buffer = new ABuffer(nalSize); - memcpy(buffer->data(), nalStart, nalSize); - return buffer; - } - } - - return NULL; -} - -sp ElementaryStreamQueue::MakeAVCCodecSpecificData( - const sp &accessUnit) { - const uint8_t *data = accessUnit->data(); - size_t size = accessUnit->size(); - - sp seqParamSet = FindNAL(data, size, 7, NULL); - if (seqParamSet == NULL) { - return NULL; - } - - int32_t width, height; - FindAVCDimensions(seqParamSet, &width, &height); - - size_t stopOffset; - sp picParamSet = FindNAL(data, size, 8, &stopOffset); - CHECK(picParamSet != NULL); - - size_t csdSize = - 1 + 3 + 1 + 1 - + 2 * 1 + seqParamSet->size() - + 1 + 2 * 1 + picParamSet->size(); - - sp csd = new ABuffer(csdSize); - uint8_t *out = csd->data(); - - *out++ = 0x01; // configurationVersion - memcpy(out, seqParamSet->data() + 1, 3); // profile/level... - out += 3; - *out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes - *out++ = 0xe0 | 1; - - *out++ = seqParamSet->size() >> 8; - *out++ = seqParamSet->size() & 0xff; - memcpy(out, seqParamSet->data(), seqParamSet->size()); - out += seqParamSet->size(); - - *out++ = 1; - - *out++ = picParamSet->size() >> 8; - *out++ = picParamSet->size() & 0xff; - memcpy(out, picParamSet->data(), picParamSet->size()); - -#if 0 - LOGI("AVC seq param set"); - hexdump(seqParamSet->data(), seqParamSet->size()); -#endif - - sp meta = new MetaData; - meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); - - meta->setData(kKeyAVCC, 0, csd->data(), csd->size()); - meta->setInt32(kKeyWidth, width); - meta->setInt32(kKeyHeight, height); - - LOGI("found AVC codec config (%d x %d)", width, height); - - return meta; -} - } // namespace android diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h index 246c390..9eaf834 100644 --- a/media/libstagefright/mpeg2ts/ESQueue.h +++ b/media/libstagefright/mpeg2ts/ESQueue.h @@ -56,9 +56,6 @@ private: unsigned profile, unsigned sampling_freq_index, unsigned channel_configuration); - static sp MakeAVCCodecSpecificData( - const sp &accessUnit); - DISALLOW_EVIL_CONSTRUCTORS(ElementaryStreamQueue); }; diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp index 78754e6..10cc88b 100644 --- a/media/libstagefright/rtsp/APacketSource.cpp +++ b/media/libstagefright/rtsp/APacketSource.cpp @@ -490,6 +490,8 @@ APacketSource::APacketSource( : mInitCheck(NO_INIT), mFormat(new MetaData), mEOSResult(OK), + mIsAVC(false), + mScanForIDR(true), mRTPTimeBase(0), mNormalPlayTimeBaseUs(0), mLastNormalPlayTimeUs(0) { @@ -509,6 +511,8 @@ APacketSource::APacketSource( mInitCheck = OK; if (!strncmp(desc.c_str(), "H264/", 5)) { + mIsAVC = true; + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); int32_t width, height; @@ -719,6 +723,20 @@ void APacketSource::queueAccessUnit(const sp &buffer) { return; } + if (mScanForIDR && mIsAVC) { + // This pretty piece of code ensures that the first access unit + // fed to the decoder after stream-start or seek is guaranteed to + // be an IDR frame. This is to workaround limitations of a certain + // hardware h.264 decoder that requires this to be the case. + + if (!IsIDR(buffer)) { + LOGV("skipping AU while scanning for next IDR frame."); + return; + } + + mScanForIDR = false; + } + Mutex::Autolock autoLock(mLock); mBuffers.push_back(buffer); mCondition.signal(); @@ -735,6 +753,8 @@ void APacketSource::signalEOS(status_t result) { void APacketSource::flushQueue() { Mutex::Autolock autoLock(mLock); mBuffers.clear(); + + mScanForIDR = true; } int64_t APacketSource::getNormalPlayTimeUs() { diff --git a/media/libstagefright/rtsp/APacketSource.h b/media/libstagefright/rtsp/APacketSource.h index 076ddc4..7a77fc6 100644 --- a/media/libstagefright/rtsp/APacketSource.h +++ b/media/libstagefright/rtsp/APacketSource.h @@ -65,6 +65,9 @@ private: List > mBuffers; status_t mEOSResult; + bool mIsAVC; + bool mScanForIDR; + uint32_t mClockRate; uint32_t mRTPTimeBase; -- cgit v1.1