From 67bdd89ba2f84b830a217b2532a9c0cdc25a30fb Mon Sep 17 00:00:00 2001 From: Thomas Inskip Date: Thu, 17 Apr 2014 18:57:31 -0700 Subject: [PATCH] Implemented H.264 byte stream to unit stream conversion and other components needed for MPEG-2 TS h.264 video demux and transmux. Change-Id: I878cdd141140cfd6833d75c7133301b1d65f1da0 --- media/base/buffer_writer.h | 2 + media/filters/filters.gyp | 3 + .../h264_byte_to_unit_stream_converter.cc | 131 ++++++++++++++++++ .../h264_byte_to_unit_stream_converter.h | 57 ++++++++ ..._byte_to_unit_stream_converter_unittest.cc | 61 ++++++++ media/formats/mp2t/es_parser_h264.cc | 127 +++++++++++------ media/formats/mp2t/es_parser_h264.h | 9 ++ .../mp2t/mp2t_media_parser_unittest.cc | 7 +- media/test/data/README | 3 + media/test/data/avc-byte-stream-frame.h264 | Bin 0 -> 5650 bytes media/test/data/avc-unit-stream-frame.h264 | Bin 0 -> 5622 bytes 11 files changed, 356 insertions(+), 44 deletions(-) create mode 100644 media/filters/h264_byte_to_unit_stream_converter.cc create mode 100644 media/filters/h264_byte_to_unit_stream_converter.h create mode 100644 media/filters/h264_byte_to_unit_stream_converter_unittest.cc create mode 100644 media/test/data/avc-byte-stream-frame.h264 create mode 100644 media/test/data/avc-unit-stream-frame.h264 diff --git a/media/base/buffer_writer.h b/media/base/buffer_writer.h index bc8141db63..39aa75d291 100644 --- a/media/base/buffer_writer.h +++ b/media/base/buffer_writer.h @@ -50,6 +50,8 @@ class BufferWriter { void AppendBuffer(const BufferWriter& buffer); void Swap(BufferWriter* buffer) { buf_.swap(buffer->buf_); } + void SwapBuffer(std::vector* buffer) { buf_.swap(*buffer); } + void Clear() { buf_.clear(); } size_t Size() const { return buf_.size(); } /// @return Underlying buffer. Behavior is undefined if the buffer size is 0. diff --git a/media/filters/filters.gyp b/media/filters/filters.gyp index d4d1ace796..81e490bf1c 100644 --- a/media/filters/filters.gyp +++ b/media/filters/filters.gyp @@ -21,6 +21,8 @@ 'sources': [ 'h264_bit_reader.cc', 'h264_bit_reader.h', + 'h264_byte_to_unit_stream_converter.cc', + 'h264_byte_to_unit_stream_converter.h', 'h264_parser.cc', 'h264_parser.h', ], @@ -33,6 +35,7 @@ 'type': '<(gtest_target_type)', 'sources': [ 'h264_bit_reader_unittest.cc', + 'h264_byte_to_unit_stream_converter_unittest.cc', 'h264_parser_unittest.cc', ], 'dependencies': [ diff --git a/media/filters/h264_byte_to_unit_stream_converter.cc b/media/filters/h264_byte_to_unit_stream_converter.cc new file mode 100644 index 0000000000..cf79118bce --- /dev/null +++ b/media/filters/h264_byte_to_unit_stream_converter.cc @@ -0,0 +1,131 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "media/filters/h264_byte_to_unit_stream_converter.h" + +#include "base/logging.h" +#include "media/base/buffer_writer.h" +#include "media/filters/h264_parser.h" + +namespace media { + +namespace { +// Additional space to reserve for output frame. This value ought to be enough +// to acommodate frames consisting of 100 NAL units with 3-byte start codes. +const size_t kStreamConversionOverhead = 100; +} + +H264ByteToUnitStreamConverter::H264ByteToUnitStreamConverter() {} + +H264ByteToUnitStreamConverter::~H264ByteToUnitStreamConverter() {} + +bool H264ByteToUnitStreamConverter::ConvertByteStreamToNalUnitStream( + const uint8* input_frame, + size_t input_frame_size, + std::vector* output_frame) { + DCHECK(input_frame); + DCHECK(output_frame); + + BufferWriter output_buffer(input_frame_size + kStreamConversionOverhead); + + const uint8* input_ptr(input_frame); + const uint8* input_end(input_ptr + input_frame_size); + off_t next_start_code_offset; + off_t next_start_code_size; + bool first_nalu(true); + while (H264Parser::FindStartCode(input_ptr, + input_end - input_ptr, + &next_start_code_offset, + &next_start_code_size)) { + if (first_nalu) { + if (next_start_code_offset != 0) { + LOG(ERROR) << "H.264 byte stream frame did not begin with start code."; + return false; + } + first_nalu = false; + } else { + ProcessNalu(input_ptr, next_start_code_offset, &output_buffer); + } + input_ptr += next_start_code_offset + next_start_code_size; + } + + if (first_nalu) { + LOG(ERROR) << "H.264 byte stream frame did not contain start codes."; + return false; + } else { + ProcessNalu(input_ptr, input_end - input_ptr, &output_buffer); + } + + output_buffer.SwapBuffer(output_frame); + return true; +} + +void H264ByteToUnitStreamConverter::ProcessNalu( + const uint8* nalu_ptr, + size_t nalu_size, + BufferWriter* output_buffer) { + DCHECK(nalu_ptr); + DCHECK(output_buffer); + + if (!nalu_size) + return; // Edge case. + + uint8 nalu_type = *nalu_ptr & 0x0f; + switch (nalu_type) { + case H264NALU::kSPS: + // Grab SPS NALU. + last_sps_.assign(nalu_ptr, nalu_ptr + nalu_size); + return; + case H264NALU::kPPS: + // Grab PPS NALU. + last_pps_.assign(nalu_ptr, nalu_ptr + nalu_size); + return; + case H264NALU::kAUD: + // Ignore AUD NALU. + return; + default: + // Copy all other NALUs. + break; + } + + // Append 4-byte length and NAL unit data to the buffer. + output_buffer->AppendInt(static_cast(nalu_size)); + output_buffer->AppendArray(nalu_ptr, nalu_size); +} + +bool H264ByteToUnitStreamConverter::GetAVCDecoderConfigurationRecord( + std::vector* decoder_config) { + DCHECK(decoder_config); + + if ((last_sps_.size() < 4) || last_pps_.empty()) { + // No data available to construct AVCDecoderConfigurationRecord. + return false; + } + + // Construct an AVCDecoderConfigurationRecord containing a single SPS and a + // single PPS NALU. Please refer to ISO/IEC 14496-15 for format specifics. + BufferWriter buffer(last_sps_.size() + last_pps_.size() + 11); + uint8 version(1); + buffer.AppendInt(version); + buffer.AppendInt(last_sps_[1]); + buffer.AppendInt(last_sps_[2]); + buffer.AppendInt(last_sps_[3]); + uint8 reserved_and_length_size_minus_one(0xff); + buffer.AppendInt(reserved_and_length_size_minus_one); + uint8 reserved_and_num_sps(0xe1); + buffer.AppendInt(reserved_and_num_sps); + buffer.AppendInt(static_cast(last_sps_.size())); + buffer.AppendVector(last_sps_); + uint8 num_pps(1); + buffer.AppendInt(num_pps); + buffer.AppendInt(static_cast(last_pps_.size())); + buffer.AppendVector(last_pps_); + buffer.SwapBuffer(decoder_config); + + return true; +} + +} // namespace media diff --git a/media/filters/h264_byte_to_unit_stream_converter.h b/media/filters/h264_byte_to_unit_stream_converter.h new file mode 100644 index 0000000000..87c28fe450 --- /dev/null +++ b/media/filters/h264_byte_to_unit_stream_converter.h @@ -0,0 +1,57 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_FILTERS_H264_BYTE_TO_UNIT_STREAM_CONVERTER_H_ +#define MEDIA_FILTERS_H264_BYTE_TO_UNIT_STREAM_CONVERTER_H_ + +#include "base/basictypes.h" + +#include + +namespace media { + +class BufferWriter; + +/// Class which converts H.264 byte streams (as specified in ISO/IEC 14496-10 +/// Annex B) into H.264 NAL unit streams (as specified in ISO/IEC 14496-15). +class H264ByteToUnitStreamConverter { + public: + static const size_t kUnitStreamNaluLengthSize = 4; + + H264ByteToUnitStreamConverter(); + ~H264ByteToUnitStreamConverter(); + + /// Converts a whole AVC byte stream encoded video frame to NAL unit stream + /// format. + /// @param input_frame is a buffer containing a whole H.264 frame in byte + /// stream format. + /// @param input_frame_size is the size of the H.264 frame, in bytes. + /// @param output_frame is a pointer to a vector which will receive the + /// converted frame. + /// @return true if successful, false otherwise. + bool ConvertByteStreamToNalUnitStream(const uint8* input_frame, + size_t input_frame_size, + std::vector* output_frame); + + /// Synthesizes an AVCDecoderConfigurationRecord from the SPS and PPS NAL + /// units extracted from the AVC byte stream. + /// @param decoder_config is a pointer to a vector, which on successful + /// return will contain the computed AVCDecoderConfigurationRecord. + /// @return true if successful, or false otherwise. + bool GetAVCDecoderConfigurationRecord(std::vector* decoder_config); + + private: + void ProcessNalu(const uint8* nalu_ptr, + size_t nalu_size, + BufferWriter* output_buffer); + + std::vector last_sps_; + std::vector last_pps_; +}; + +} // namespace media + +#endif // MEDIA_FILTERS_H264_BYTE_TO_UNIT_STREAM_CONVERTER_H_ diff --git a/media/filters/h264_byte_to_unit_stream_converter_unittest.cc b/media/filters/h264_byte_to_unit_stream_converter_unittest.cc new file mode 100644 index 0000000000..e62d976db4 --- /dev/null +++ b/media/filters/h264_byte_to_unit_stream_converter_unittest.cc @@ -0,0 +1,61 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "base/strings/string_number_conversions.h" +#include "media/filters/h264_byte_to_unit_stream_converter.h" +#include "media/test/test_data_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include + +namespace { +const char kExpectedConfigRecord[] = + "014d400dffe10013274d400da918283e600d418041adb0ad7bdf01010004" + "28de0988"; +} + +namespace media { + +TEST(H264ByteToUnitStreamConverter, ConversionSuccess) { + std::vector input_frame = + ReadTestDataFile("avc-byte-stream-frame.h264"); + ASSERT_FALSE(input_frame.empty()); + + std::vector expected_output_frame = + ReadTestDataFile("avc-unit-stream-frame.h264"); + ASSERT_FALSE(expected_output_frame.empty()); + + H264ByteToUnitStreamConverter converter; + std::vector output_frame; + ASSERT_TRUE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + input_frame.size(), + &output_frame)); + EXPECT_EQ(expected_output_frame, output_frame); + + std::vector expected_decoder_config; + ASSERT_TRUE(base::HexStringToBytes(kExpectedConfigRecord, + &expected_decoder_config)); + std::vector decoder_config; + ASSERT_TRUE(converter.GetAVCDecoderConfigurationRecord(&decoder_config)); + EXPECT_EQ(expected_decoder_config, decoder_config); +} + +TEST(H264ByteToUnitStreamConverter, ConversionFailure) { + std::vector input_frame(100, 0); + + H264ByteToUnitStreamConverter converter; + std::vector output_frame; + EXPECT_FALSE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + 0, + &output_frame)); + EXPECT_FALSE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + input_frame.size(), + &output_frame)); + std::vector decoder_config; + EXPECT_FALSE(converter.GetAVCDecoderConfigurationRecord(&decoder_config)); +} + +} // namespace media diff --git a/media/formats/mp2t/es_parser_h264.cc b/media/formats/mp2t/es_parser_h264.cc index c0aca48c92..b4380b573a 100644 --- a/media/formats/mp2t/es_parser_h264.cc +++ b/media/formats/mp2t/es_parser_h264.cc @@ -11,6 +11,7 @@ #include "media/base/offset_byte_queue.h" #include "media/base/timestamp.h" #include "media/base/video_stream_info.h" +#include "media/filters/h264_byte_to_unit_stream_converter.h" #include "media/filters/h264_parser.h" #include "media/formats/mp2t/mp2t_common.h" @@ -38,7 +39,10 @@ EsParserH264::EsParserH264( es_queue_(new media::OffsetByteQueue()), h264_parser_(new H264Parser()), current_access_unit_pos_(0), - next_access_unit_pos_(0) { + next_access_unit_pos_(0), + stream_converter_(new H264ByteToUnitStreamConverter), + decoder_config_check_pending_(false), + pending_sample_duration_(0) { } EsParserH264::~EsParserH264() { @@ -72,14 +76,22 @@ bool EsParserH264::Parse(const uint8* buf, int size, int64 pts, int64 dts) { void EsParserH264::Flush() { DVLOG(1) << "EsParserH264::Flush"; - if (!FindAUD(¤t_access_unit_pos_)) - return; - // Simulate an additional AUD to force emitting the last access unit - // which is assumed to be complete at this point. - uint8 aud[] = { 0x00, 0x00, 0x01, 0x09 }; - es_queue_->Push(aud, sizeof(aud)); - ParseInternal(); + if (FindAUD(¤t_access_unit_pos_)) { + // Simulate an additional AUD to force emitting the last access unit + // which is assumed to be complete at this point. + uint8 aud[] = { 0x00, 0x00, 0x01, 0x09 }; + es_queue_->Push(aud, sizeof(aud)); + ParseInternal(); + } + + if (pending_sample_) { + // Flush pending sample. + DCHECK(pending_sample_duration_); + pending_sample_->set_duration(pending_sample_duration_); + emit_sample_cb_.Run(pid(), pending_sample_); + pending_sample_ = scoped_refptr(); + } } void EsParserH264::Reset() { @@ -90,6 +102,9 @@ void EsParserH264::Reset() { next_access_unit_pos_ = 0; timing_desc_list_.clear(); last_video_decoder_config_ = scoped_refptr(); + decoder_config_check_pending_ = false; + pending_sample_ = scoped_refptr(); + pending_sample_duration_ = 0; } bool EsParserH264::FindAUD(int64* stream_pos) { @@ -189,6 +204,7 @@ bool EsParserH264::ParseInternal() { int sps_id; if (h264_parser_->ParseSPS(&sps_id) != H264Parser::kOk) return false; + decoder_config_check_pending_ = true; break; } case H264NALU::kPPS: { @@ -196,6 +212,7 @@ bool EsParserH264::ParseInternal() { int pps_id; if (h264_parser_->ParsePPS(&pps_id) != H264Parser::kOk) return false; + decoder_config_check_pending_ = true; break; } case H264NALU::kIDRSlice: @@ -242,23 +259,6 @@ bool EsParserH264::EmitFrame(int64 access_unit_pos, int access_unit_size, if (current_timing_desc.pts == kNoTimestamp) return false; - // Update the video decoder configuration if needed. - const H264PPS* pps = h264_parser_->GetPPS(pps_id); - if (!pps) { - // Only accept an invalid PPS at the beginning when the stream - // does not necessarily start with an SPS/PPS/IDR. - // In this case, the initial frames are conveyed to the upper layer with - // an invalid VideoDecoderConfig and it's up to the upper layer - // to process this kind of frame accordingly. - if (last_video_decoder_config_) - return false; - } else { - const H264SPS* sps = h264_parser_->GetSPS(pps->seq_parameter_set_id); - if (!sps) - return false; - RCHECK(UpdateVideoDecoderConfig(sps)); - } - // Emit a frame. DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_ << " size=" << access_unit_size; @@ -267,25 +267,67 @@ bool EsParserH264::EmitFrame(int64 access_unit_pos, int access_unit_size, es_queue_->PeekAt(current_access_unit_pos_, &es, &es_size); CHECK_GE(es_size, access_unit_size); - // TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId - // type and allow multiple video tracks. See https://crbug.com/341581. - scoped_refptr media_sample = - MediaSample::CopyFrom( - es, - access_unit_size, - is_key_frame); + // Convert frame to unit stream format. + std::vector converted_frame; + if (!stream_converter_->ConvertByteStreamToNalUnitStream( + es, access_unit_size, &converted_frame)) { + DLOG(ERROR) << "Failure to convert video frame to unit stream format."; + return false; + } + + if (decoder_config_check_pending_) { + // Update the video decoder configuration if needed. + const H264PPS* pps = h264_parser_->GetPPS(pps_id); + if (!pps) { + // Only accept an invalid PPS at the beginning when the stream + // does not necessarily start with an SPS/PPS/IDR. + // In this case, the initial frames are conveyed to the upper layer with + // an invalid VideoDecoderConfig and it's up to the upper layer + // to process this kind of frame accordingly. + if (last_video_decoder_config_) + return false; + } else { + const H264SPS* sps = h264_parser_->GetSPS(pps->seq_parameter_set_id); + if (!sps) + return false; + RCHECK(UpdateVideoDecoderConfig(sps)); + decoder_config_check_pending_ = false; + } + } + + // Create the media sample, emitting always the previous sample after + // calculating its duration. + scoped_refptr media_sample = MediaSample::CopyFrom( + converted_frame.data(), converted_frame.size(), is_key_frame); media_sample->set_dts(current_timing_desc.dts); media_sample->set_pts(current_timing_desc.pts); - emit_sample_cb_.Run(pid(), media_sample); + if (pending_sample_) { + DCHECK_GT(media_sample->dts(), pending_sample_->dts()); + pending_sample_duration_ = media_sample->dts() - pending_sample_->dts(); + pending_sample_->set_duration(pending_sample_duration_); + emit_sample_cb_.Run(pid(), pending_sample_); + } + pending_sample_ = media_sample; + return true; } bool EsParserH264::UpdateVideoDecoderConfig(const H264SPS* sps) { - // TODO(tinskip): Generate an error if video configuration change is detected. + std::vector decoder_config_record; + if (!stream_converter_->GetAVCDecoderConfigurationRecord( + &decoder_config_record)) { + DLOG(ERROR) << "Failure to construct an AVCDecoderConfigurationRecord"; + return false; + } + if (last_video_decoder_config_) { - // Varying video configurations currently not supported. Just assume that - // the video configuration has not changed. - return true; + // Verify that the video decoder config has not changed. + if (last_video_decoder_config_->extra_data() == decoder_config_record) { + // Video configuration has not changed. + return true; + } + NOTIMPLEMENTED() << "Varying video configurations are not supported."; + return false; } // TODO(damienv): a MAP unit can be either 16 or 32 pixels. @@ -299,13 +341,16 @@ bool EsParserH264::UpdateVideoDecoderConfig(const H264SPS* sps) { kMpeg2Timescale, kInfiniteDuration, kCodecH264, - std::string(), // TODO(tinskip): calculate codec string. + VideoStreamInfo::GetCodecString(kCodecH264, + decoder_config_record[1], + decoder_config_record[2], + decoder_config_record[3]), std::string(), width, height, - kCommonNaluLengthSize, - NULL, // TODO(tinskip): calculate AVCDecoderConfigurationRecord. - 0, + H264ByteToUnitStreamConverter::kUnitStreamNaluLengthSize, + decoder_config_record.data(), + decoder_config_record.size(), false)); DVLOG(1) << "Profile IDC: " << sps->profile_idc; DVLOG(1) << "Level IDC: " << sps->level_idc; diff --git a/media/formats/mp2t/es_parser_h264.h b/media/formats/mp2t/es_parser_h264.h index 11bb8c4393..8f5ab7f85c 100644 --- a/media/formats/mp2t/es_parser_h264.h +++ b/media/formats/mp2t/es_parser_h264.h @@ -16,6 +16,7 @@ namespace media { +class H264ByteToUnitStreamConverter; class H264Parser; class OffsetByteQueue; struct H264SPS; @@ -83,8 +84,16 @@ class EsParserH264 : public EsParser { int64 current_access_unit_pos_; int64 next_access_unit_pos_; + // Filter to convert H.264 Annex B byte stream to unit stream. + scoped_ptr stream_converter_; + // Last video decoder config. scoped_refptr last_video_decoder_config_; + bool decoder_config_check_pending_; + + // Frame for which we do not yet have a duration. + scoped_refptr pending_sample_; + uint64 pending_sample_duration_; }; } // namespace mp2t diff --git a/media/formats/mp2t/mp2t_media_parser_unittest.cc b/media/formats/mp2t/mp2t_media_parser_unittest.cc index 6d4e64d620..34726cc869 100644 --- a/media/formats/mp2t/mp2t_media_parser_unittest.cc +++ b/media/formats/mp2t/mp2t_media_parser_unittest.cc @@ -126,7 +126,7 @@ class Mp2tMediaParserTest : public testing::Test { TEST_F(Mp2tMediaParserTest, UnalignedAppend17) { // Test small, non-segment-aligned appends. ParseMpeg2TsFile("bear-1280x720.ts", 17); - EXPECT_EQ(video_frame_count_, 81); + EXPECT_EQ(video_frame_count_, 80); parser_->Flush(); EXPECT_EQ(video_frame_count_, 82); } @@ -134,7 +134,7 @@ TEST_F(Mp2tMediaParserTest, UnalignedAppend17) { TEST_F(Mp2tMediaParserTest, UnalignedAppend512) { // Test small, non-segment-aligned appends. ParseMpeg2TsFile("bear-1280x720.ts", 512); - EXPECT_EQ(video_frame_count_, 81); + EXPECT_EQ(video_frame_count_, 80); parser_->Flush(); EXPECT_EQ(video_frame_count_, 82); } @@ -145,7 +145,8 @@ TEST_F(Mp2tMediaParserTest, TimestampWrapAround) { // (close to 2^33 / 90000) which results in timestamps wrap around // in the Mpeg2 TS stream. ParseMpeg2TsFile("bear-1280x720_ptswraparound.ts", 512); - EXPECT_EQ(video_frame_count_, 81); + parser_->Flush(); + EXPECT_EQ(video_frame_count_, 82); EXPECT_GE(video_min_dts_, (95443 - 1) * kMpeg2Timescale); EXPECT_LE(video_max_dts_, static_cast((95443 + 4)) * kMpeg2Timescale); diff --git a/media/test/data/README b/media/test/data/README index 105c8dbac4..e3e8699710 100644 --- a/media/test/data/README +++ b/media/test/data/README @@ -74,3 +74,6 @@ bear.h264: bear.mp4 (https://chromiumcodereview.appspot.com/10805089): ffmpeg -i bear.mp4 -vcodec copy -vbsf h264_mp4toannexb \ -an bear.h264 + +avc-byte-stream-frame.h264 - Single IDR frame extracted from test-25fps.h264 in Annex B byte stream format. +avc-unit-stream-frame.h264 - Single IDR frame from avc-byte-stream-frame.h264 converted to unit stream format. diff --git a/media/test/data/avc-byte-stream-frame.h264 b/media/test/data/avc-byte-stream-frame.h264 new file mode 100644 index 0000000000000000000000000000000000000000..0a91e6fa4315dfc4dc3d55f80e638c369e3558ee GIT binary patch literal 5650 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 literal 0 HcmV?d00001 diff --git a/media/test/data/avc-unit-stream-frame.h264 b/media/test/data/avc-unit-stream-frame.h264 new file mode 100644 index 0000000000000000000000000000000000000000..ab7a09492abdc7aefdd69c5b27ddb8a4648035ec GIT binary patch literal 5622 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