Search for SPS NALU rather than assuming its position

Summary:
The implementation of H264AnnexBBufferHasVideoFormatDescription was
assuming that the SPS NALU is either the first NALU in the stream, or
the second one, in case an AUD NALU is present in the first location.
This change removes this assumption and instead searches for the SPS
NALU, failing only if we can't find one.

In addition, it cleans up some binary buffer manipulation code, using the
the parsed NALU indices we already have in AnnexBBufferReader instead.

Test Plan: Unit tests

Change-Id: Id9715aa1d751f0ba1a1992def2b690607896df56

bug: webrtc:8922
Change-Id: Id9715aa1d751f0ba1a1992def2b690607896df56
Reviewed-on: https://webrtc-review.googlesource.com/49982
Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Kári Helgason <kthelgason@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22205}
This commit is contained in:
Guy Hershenbaum
2018-02-20 21:33:36 -08:00
committed by Commit Bot
parent 0efa941d2f
commit 2fcb834bb4
4 changed files with 98 additions and 81 deletions

View File

@ -120,8 +120,6 @@ void decompressionOutputCallback(void *decoderRef,
return WEBRTC_VIDEO_CODEC_NO_OUTPUT; return WEBRTC_VIDEO_CODEC_NO_OUTPUT;
} }
#endif #endif
if (webrtc::H264AnnexBBufferHasVideoFormatDescription((uint8_t *)inputImage.buffer.bytes,
inputImage.buffer.length)) {
rtc::ScopedCFTypeRef<CMVideoFormatDescriptionRef> inputFormat = rtc::ScopedCFTypeRef<CMVideoFormatDescriptionRef> inputFormat =
rtc::ScopedCF(webrtc::CreateVideoFormatDescription((uint8_t *)inputImage.buffer.bytes, rtc::ScopedCF(webrtc::CreateVideoFormatDescription((uint8_t *)inputImage.buffer.bytes,
inputImage.buffer.length)); inputImage.buffer.length));
@ -130,14 +128,12 @@ void decompressionOutputCallback(void *decoderRef,
// needed. // needed.
if (!CMFormatDescriptionEqual(inputFormat.get(), _videoFormat)) { if (!CMFormatDescriptionEqual(inputFormat.get(), _videoFormat)) {
[self setVideoFormat:inputFormat.get()]; [self setVideoFormat:inputFormat.get()];
int resetDecompressionSessionError = [self resetDecompressionSession]; int resetDecompressionSessionError = [self resetDecompressionSession];
if (resetDecompressionSessionError != WEBRTC_VIDEO_CODEC_OK) { if (resetDecompressionSessionError != WEBRTC_VIDEO_CODEC_OK) {
return resetDecompressionSessionError; return resetDecompressionSessionError;
} }
} }
} }
}
if (!_videoFormat) { if (!_videoFormat) {
// We received a frame but we don't have format information so we can't // We received a frame but we don't have format information so we can't
// decode it. // decode it.

View File

@ -167,11 +167,10 @@ bool H264AnnexBBufferToCMSampleBuffer(const uint8_t* annexb_buffer,
*out_sample_buffer = nullptr; *out_sample_buffer = nullptr;
AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size); AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size);
if (H264AnnexBBufferHasVideoFormatDescription(annexb_buffer, if (reader.SeekToNextNaluOfType(kSps)) {
annexb_buffer_size)) { // Buffer contains an SPS NALU - skip it and the following PPS
// Advance past the SPS and PPS. const uint8_t* data;
const uint8_t* data = nullptr; size_t data_len;
size_t data_len = 0;
if (!reader.ReadNalu(&data, &data_len)) { if (!reader.ReadNalu(&data, &data_len)) {
RTC_LOG(LS_ERROR) << "Failed to read SPS"; RTC_LOG(LS_ERROR) << "Failed to read SPS";
return false; return false;
@ -180,6 +179,9 @@ bool H264AnnexBBufferToCMSampleBuffer(const uint8_t* annexb_buffer,
RTC_LOG(LS_ERROR) << "Failed to read PPS"; RTC_LOG(LS_ERROR) << "Failed to read PPS";
return false; return false;
} }
} else {
// No SPS NALU - start reading from the first NALU in the buffer
reader.SeekToStart();
} }
// Allocate memory as a block buffer. // Allocate memory as a block buffer.
@ -246,48 +248,16 @@ bool H264AnnexBBufferToCMSampleBuffer(const uint8_t* annexb_buffer,
return true; return true;
} }
bool H264AnnexBBufferHasVideoFormatDescription(const uint8_t* annexb_buffer,
size_t annexb_buffer_size) {
RTC_DCHECK(annexb_buffer);
RTC_DCHECK_GT(annexb_buffer_size, 4);
// The buffer we receive via RTP has 00 00 00 01 start code artifically
// embedded by the RTP depacketizer. Extract NALU information.
// TODO(tkchin): handle potential case where sps and pps are delivered
// separately.
NaluType first_nalu_type = ParseNaluType(annexb_buffer[4]);
bool is_first_nalu_type_sps = first_nalu_type == kSps;
if (is_first_nalu_type_sps)
return true;
bool is_first_nalu_type_aud = first_nalu_type == kAud;
// Start code + access unit delimiter + start code = 4 + 2 + 4 = 10.
if (!is_first_nalu_type_aud || annexb_buffer_size <= 10u)
return false;
NaluType second_nalu_type = ParseNaluType(annexb_buffer[10]);
bool is_second_nalu_type_sps = second_nalu_type == kSps;
return is_second_nalu_type_sps;
}
CMVideoFormatDescriptionRef CreateVideoFormatDescription( CMVideoFormatDescriptionRef CreateVideoFormatDescription(
const uint8_t* annexb_buffer, const uint8_t* annexb_buffer,
size_t annexb_buffer_size) { size_t annexb_buffer_size) {
if (!H264AnnexBBufferHasVideoFormatDescription(annexb_buffer,
annexb_buffer_size)) {
return nullptr;
}
AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size);
CMVideoFormatDescriptionRef description = nullptr;
OSStatus status = noErr;
// Parse the SPS and PPS into a CMVideoFormatDescription.
const uint8_t* param_set_ptrs[2] = {}; const uint8_t* param_set_ptrs[2] = {};
size_t param_set_sizes[2] = {}; size_t param_set_sizes[2] = {};
// Skip AUD. AnnexBBufferReader reader(annexb_buffer, annexb_buffer_size);
if (ParseNaluType(annexb_buffer[4]) == kAud) { // Skip everyting before the SPS, then read the SPS and PPS
if (!reader.ReadNalu(&param_set_ptrs[0], &param_set_sizes[0])) { if (!reader.SeekToNextNaluOfType(kSps)) {
RTC_LOG(LS_ERROR) << "Failed to read AUD";
return nullptr; return nullptr;
} }
}
if (!reader.ReadNalu(&param_set_ptrs[0], &param_set_sizes[0])) { if (!reader.ReadNalu(&param_set_ptrs[0], &param_set_sizes[0])) {
RTC_LOG(LS_ERROR) << "Failed to read SPS"; RTC_LOG(LS_ERROR) << "Failed to read SPS";
return nullptr; return nullptr;
@ -296,9 +266,11 @@ CMVideoFormatDescriptionRef CreateVideoFormatDescription(
RTC_LOG(LS_ERROR) << "Failed to read PPS"; RTC_LOG(LS_ERROR) << "Failed to read PPS";
return nullptr; return nullptr;
} }
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
kCFAllocatorDefault, 2, param_set_ptrs, param_set_sizes, 4, // Parse the SPS and PPS into a CMVideoFormatDescription.
&description); CMVideoFormatDescriptionRef description = nullptr;
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
kCFAllocatorDefault, 2, param_set_ptrs, param_set_sizes, 4, &description);
if (status != noErr) { if (status != noErr) {
RTC_LOG(LS_ERROR) << "Failed to create video format description."; RTC_LOG(LS_ERROR) << "Failed to create video format description.";
return nullptr; return nullptr;
@ -337,6 +309,19 @@ size_t AnnexBBufferReader::BytesRemaining() const {
return length_ - offset_->start_offset; return length_ - offset_->start_offset;
} }
void AnnexBBufferReader::SeekToStart() {
offset_ = offsets_.begin();
}
bool AnnexBBufferReader::SeekToNextNaluOfType(NaluType type) {
for (; offset_ != offsets_.end(); ++offset_) {
if (offset_->payload_size < 1)
continue;
if (ParseNaluType(*(start_ + offset_->payload_start_offset)) == type)
return true;
}
return false;
}
AvccBufferWriter::AvccBufferWriter(uint8_t* const avcc_buffer, size_t length) AvccBufferWriter::AvccBufferWriter(uint8_t* const avcc_buffer, size_t length)
: start_(avcc_buffer), offset_(0), length_(length) { : start_(avcc_buffer), offset_(0), length_(length) {
RTC_DCHECK(avcc_buffer); RTC_DCHECK(avcc_buffer);

View File

@ -46,11 +46,6 @@ bool H264AnnexBBufferToCMSampleBuffer(const uint8_t* annexb_buffer,
CMVideoFormatDescriptionRef video_format, CMVideoFormatDescriptionRef video_format,
CMSampleBufferRef* out_sample_buffer); CMSampleBufferRef* out_sample_buffer);
// Returns true if the type of the first NALU in the supplied Annex B buffer is
// the SPS type.
bool H264AnnexBBufferHasVideoFormatDescription(const uint8_t* annexb_buffer,
size_t annexb_buffer_size);
// Returns a video format description created from the sps/pps information in // Returns a video format description created from the sps/pps information in
// the Annex B buffer. If there is no such information, nullptr is returned. // the Annex B buffer. If there is no such information, nullptr is returned.
// The caller is responsible for releasing the description. // The caller is responsible for releasing the description.
@ -74,6 +69,15 @@ class AnnexBBufferReader final {
// If the buffer has no remaining NALUs this will return zero. // If the buffer has no remaining NALUs this will return zero.
size_t BytesRemaining() const; size_t BytesRemaining() const;
// Reset the reader to start reading from the first NALU
void SeekToStart();
// Seek to the next position that holds a NALU of the desired type,
// or the end if no such NALU is found.
// Return true if a NALU of the desired type is found, false if we
// reached the end instead
bool SeekToNextNaluOfType(H264::NaluType type);
private: private:
// Returns the the next offset that contains NALU data. // Returns the the next offset that contains NALU data.
size_t FindNextNaluHeader(const uint8_t* start, size_t FindNextNaluHeader(const uint8_t* start,

View File

@ -11,32 +11,18 @@
#include <memory> #include <memory>
#include "common_video/h264/h264_common.h"
#include "rtc_base/arraysize.h" #include "rtc_base/arraysize.h"
#include "sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h" #include "sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h"
#include "test/gtest.h" #include "test/gtest.h"
namespace webrtc { namespace webrtc {
using H264::kSps;
static const uint8_t NALU_TEST_DATA_0[] = {0xAA, 0xBB, 0xCC}; static const uint8_t NALU_TEST_DATA_0[] = {0xAA, 0xBB, 0xCC};
static const uint8_t NALU_TEST_DATA_1[] = {0xDE, 0xAD, 0xBE, 0xEF}; static const uint8_t NALU_TEST_DATA_1[] = {0xDE, 0xAD, 0xBE, 0xEF};
TEST(H264VideoToolboxNaluTest, TestHasVideoFormatDescription) {
const uint8_t sps_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x27};
EXPECT_TRUE(H264AnnexBBufferHasVideoFormatDescription(sps_buffer,
arraysize(sps_buffer)));
const uint8_t aud_sps_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x29, 0x10,
0x00, 0x00, 0x00, 0x01, 0x27, 0xFF};
EXPECT_TRUE(H264AnnexBBufferHasVideoFormatDescription(
aud_sps_buffer, arraysize(aud_sps_buffer)));
const uint8_t other_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x28};
EXPECT_FALSE(H264AnnexBBufferHasVideoFormatDescription(
other_buffer, arraysize(other_buffer)));
const uint8_t aud_other_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x29,
0x00, 0x00, 0x00, 0x01, 0x28};
EXPECT_FALSE(H264AnnexBBufferHasVideoFormatDescription(
aud_other_buffer, arraysize(aud_other_buffer)));
}
TEST(H264VideoToolboxNaluTest, TestCreateVideoFormatDescription) { TEST(H264VideoToolboxNaluTest, TestCreateVideoFormatDescription) {
const uint8_t sps_pps_buffer[] = { const uint8_t sps_pps_buffer[] = {
// SPS nalu. // SPS nalu.
@ -54,6 +40,24 @@ TEST(H264VideoToolboxNaluTest, TestCreateVideoFormatDescription) {
CFRelease(description); CFRelease(description);
description = nullptr; description = nullptr;
} }
const uint8_t sps_pps_not_at_start_buffer[] = {
// Add some non-SPS/PPS NALUs at the beginning
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x01,
0xAB, 0x33, 0x21,
// SPS nalu.
0x00, 0x00, 0x01, 0x27, 0x42, 0x00, 0x1E, 0xAB, 0x40, 0xF0, 0x28, 0xD3,
0x70, 0x20, 0x20, 0x20, 0x20,
// PPS nalu.
0x00, 0x00, 0x01, 0x28, 0xCE, 0x3C, 0x30};
description = CreateVideoFormatDescription(
sps_pps_not_at_start_buffer, arraysize(sps_pps_not_at_start_buffer));
EXPECT_TRUE(description);
if (description) {
CFRelease(description);
description = nullptr;
}
const uint8_t other_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x28}; const uint8_t other_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x28};
EXPECT_FALSE(CreateVideoFormatDescription(other_buffer, EXPECT_FALSE(CreateVideoFormatDescription(other_buffer,
arraysize(other_buffer))); arraysize(other_buffer)));
@ -140,6 +144,34 @@ TEST(AnnexBBufferReaderTest, TestReadMultipleNalus) {
EXPECT_EQ(0u, nalu_length); EXPECT_EQ(0u, nalu_length);
} }
TEST(AnnexBBufferReaderTest, TestFindNextNaluOfType) {
const uint8_t notSps = 0x1F;
const uint8_t annex_b_test_data[] = {
0x00, 0x00, 0x00, 0x01, kSps, 0x00, 0x00, 0x01, notSps,
0x00, 0x00, 0x01, notSps, 0xDD, 0x00, 0x00, 0x01, notSps,
0xEE, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, kSps, 0xBB, 0x00, 0x00,
0x01, notSps, 0x00, 0x00, 0x01, notSps, 0xDD, 0x00, 0x00,
0x01, notSps, 0xEE, 0xFF, 0x00, 0x00, 0x00, 0x01};
AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data));
const uint8_t* nalu = nullptr;
size_t nalu_length = 0;
EXPECT_EQ(arraysize(annex_b_test_data), reader.BytesRemaining());
EXPECT_TRUE(reader.FindNextNaluOfType(kSps));
EXPECT_TRUE(reader.ReadNalu(&nalu, &nalu_length));
EXPECT_EQ(annex_b_test_data + 4, nalu);
EXPECT_EQ(1u, nalu_length);
EXPECT_TRUE(reader.FindNextNaluOfType(kSps));
EXPECT_TRUE(reader.ReadNalu(&nalu, &nalu_length));
EXPECT_EQ(annex_b_test_data + 32, nalu);
EXPECT_EQ(2u, nalu_length);
EXPECT_FALSE(reader.FindNextNaluOfType(kSps));
EXPECT_FALSE(reader.ReadNalu(&nalu, &nalu_length));
}
TEST(AvccBufferWriterTest, TestEmptyOutputBuffer) { TEST(AvccBufferWriterTest, TestEmptyOutputBuffer) {
const uint8_t expected_buffer[] = {0x00}; const uint8_t expected_buffer[] = {0x00};
const size_t buffer_size = 1; const size_t buffer_size = 1;