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
This commit is contained in:
Thomas Inskip 2014-04-17 18:57:31 -07:00
parent 4a39a0fc98
commit 67bdd89ba2
11 changed files with 356 additions and 44 deletions

View File

@ -50,6 +50,8 @@ class BufferWriter {
void AppendBuffer(const BufferWriter& buffer); void AppendBuffer(const BufferWriter& buffer);
void Swap(BufferWriter* buffer) { buf_.swap(buffer->buf_); } void Swap(BufferWriter* buffer) { buf_.swap(buffer->buf_); }
void SwapBuffer(std::vector<uint8>* buffer) { buf_.swap(*buffer); }
void Clear() { buf_.clear(); } void Clear() { buf_.clear(); }
size_t Size() const { return buf_.size(); } size_t Size() const { return buf_.size(); }
/// @return Underlying buffer. Behavior is undefined if the buffer size is 0. /// @return Underlying buffer. Behavior is undefined if the buffer size is 0.

View File

@ -21,6 +21,8 @@
'sources': [ 'sources': [
'h264_bit_reader.cc', 'h264_bit_reader.cc',
'h264_bit_reader.h', 'h264_bit_reader.h',
'h264_byte_to_unit_stream_converter.cc',
'h264_byte_to_unit_stream_converter.h',
'h264_parser.cc', 'h264_parser.cc',
'h264_parser.h', 'h264_parser.h',
], ],
@ -33,6 +35,7 @@
'type': '<(gtest_target_type)', 'type': '<(gtest_target_type)',
'sources': [ 'sources': [
'h264_bit_reader_unittest.cc', 'h264_bit_reader_unittest.cc',
'h264_byte_to_unit_stream_converter_unittest.cc',
'h264_parser_unittest.cc', 'h264_parser_unittest.cc',
], ],
'dependencies': [ 'dependencies': [

View File

@ -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<uint8>* 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<uint32>(nalu_size));
output_buffer->AppendArray(nalu_ptr, nalu_size);
}
bool H264ByteToUnitStreamConverter::GetAVCDecoderConfigurationRecord(
std::vector<uint8>* 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<uint16>(last_sps_.size()));
buffer.AppendVector(last_sps_);
uint8 num_pps(1);
buffer.AppendInt(num_pps);
buffer.AppendInt(static_cast<uint16>(last_pps_.size()));
buffer.AppendVector(last_pps_);
buffer.SwapBuffer(decoder_config);
return true;
}
} // namespace media

View File

@ -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 <vector>
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<uint8>* 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<uint8>* decoder_config);
private:
void ProcessNalu(const uint8* nalu_ptr,
size_t nalu_size,
BufferWriter* output_buffer);
std::vector<uint8> last_sps_;
std::vector<uint8> last_pps_;
};
} // namespace media
#endif // MEDIA_FILTERS_H264_BYTE_TO_UNIT_STREAM_CONVERTER_H_

View File

@ -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 <stdio.h>
namespace {
const char kExpectedConfigRecord[] =
"014d400dffe10013274d400da918283e600d418041adb0ad7bdf01010004"
"28de0988";
}
namespace media {
TEST(H264ByteToUnitStreamConverter, ConversionSuccess) {
std::vector<uint8> input_frame =
ReadTestDataFile("avc-byte-stream-frame.h264");
ASSERT_FALSE(input_frame.empty());
std::vector<uint8> expected_output_frame =
ReadTestDataFile("avc-unit-stream-frame.h264");
ASSERT_FALSE(expected_output_frame.empty());
H264ByteToUnitStreamConverter converter;
std::vector<uint8> output_frame;
ASSERT_TRUE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(),
input_frame.size(),
&output_frame));
EXPECT_EQ(expected_output_frame, output_frame);
std::vector<uint8> expected_decoder_config;
ASSERT_TRUE(base::HexStringToBytes(kExpectedConfigRecord,
&expected_decoder_config));
std::vector<uint8> decoder_config;
ASSERT_TRUE(converter.GetAVCDecoderConfigurationRecord(&decoder_config));
EXPECT_EQ(expected_decoder_config, decoder_config);
}
TEST(H264ByteToUnitStreamConverter, ConversionFailure) {
std::vector<uint8> input_frame(100, 0);
H264ByteToUnitStreamConverter converter;
std::vector<uint8> 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<uint8> decoder_config;
EXPECT_FALSE(converter.GetAVCDecoderConfigurationRecord(&decoder_config));
}
} // namespace media

View File

@ -11,6 +11,7 @@
#include "media/base/offset_byte_queue.h" #include "media/base/offset_byte_queue.h"
#include "media/base/timestamp.h" #include "media/base/timestamp.h"
#include "media/base/video_stream_info.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/filters/h264_parser.h"
#include "media/formats/mp2t/mp2t_common.h" #include "media/formats/mp2t/mp2t_common.h"
@ -38,7 +39,10 @@ EsParserH264::EsParserH264(
es_queue_(new media::OffsetByteQueue()), es_queue_(new media::OffsetByteQueue()),
h264_parser_(new H264Parser()), h264_parser_(new H264Parser()),
current_access_unit_pos_(0), 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() { EsParserH264::~EsParserH264() {
@ -72,9 +76,8 @@ bool EsParserH264::Parse(const uint8* buf, int size, int64 pts, int64 dts) {
void EsParserH264::Flush() { void EsParserH264::Flush() {
DVLOG(1) << "EsParserH264::Flush"; DVLOG(1) << "EsParserH264::Flush";
if (!FindAUD(&current_access_unit_pos_))
return;
if (FindAUD(&current_access_unit_pos_)) {
// Simulate an additional AUD to force emitting the last access unit // Simulate an additional AUD to force emitting the last access unit
// which is assumed to be complete at this point. // which is assumed to be complete at this point.
uint8 aud[] = { 0x00, 0x00, 0x01, 0x09 }; uint8 aud[] = { 0x00, 0x00, 0x01, 0x09 };
@ -82,6 +85,15 @@ void EsParserH264::Flush() {
ParseInternal(); 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<MediaSample>();
}
}
void EsParserH264::Reset() { void EsParserH264::Reset() {
DVLOG(1) << "EsParserH264::Reset"; DVLOG(1) << "EsParserH264::Reset";
es_queue_.reset(new media::OffsetByteQueue()); es_queue_.reset(new media::OffsetByteQueue());
@ -90,6 +102,9 @@ void EsParserH264::Reset() {
next_access_unit_pos_ = 0; next_access_unit_pos_ = 0;
timing_desc_list_.clear(); timing_desc_list_.clear();
last_video_decoder_config_ = scoped_refptr<StreamInfo>(); last_video_decoder_config_ = scoped_refptr<StreamInfo>();
decoder_config_check_pending_ = false;
pending_sample_ = scoped_refptr<MediaSample>();
pending_sample_duration_ = 0;
} }
bool EsParserH264::FindAUD(int64* stream_pos) { bool EsParserH264::FindAUD(int64* stream_pos) {
@ -189,6 +204,7 @@ bool EsParserH264::ParseInternal() {
int sps_id; int sps_id;
if (h264_parser_->ParseSPS(&sps_id) != H264Parser::kOk) if (h264_parser_->ParseSPS(&sps_id) != H264Parser::kOk)
return false; return false;
decoder_config_check_pending_ = true;
break; break;
} }
case H264NALU::kPPS: { case H264NALU::kPPS: {
@ -196,6 +212,7 @@ bool EsParserH264::ParseInternal() {
int pps_id; int pps_id;
if (h264_parser_->ParsePPS(&pps_id) != H264Parser::kOk) if (h264_parser_->ParsePPS(&pps_id) != H264Parser::kOk)
return false; return false;
decoder_config_check_pending_ = true;
break; break;
} }
case H264NALU::kIDRSlice: case H264NALU::kIDRSlice:
@ -242,6 +259,23 @@ bool EsParserH264::EmitFrame(int64 access_unit_pos, int access_unit_size,
if (current_timing_desc.pts == kNoTimestamp) if (current_timing_desc.pts == kNoTimestamp)
return false; return false;
// Emit a frame.
DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_
<< " size=" << access_unit_size;
int es_size;
const uint8* es;
es_queue_->PeekAt(current_access_unit_pos_, &es, &es_size);
CHECK_GE(es_size, access_unit_size);
// Convert frame to unit stream format.
std::vector<uint8> 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. // Update the video decoder configuration if needed.
const H264PPS* pps = h264_parser_->GetPPS(pps_id); const H264PPS* pps = h264_parser_->GetPPS(pps_id);
if (!pps) { if (!pps) {
@ -257,36 +291,44 @@ bool EsParserH264::EmitFrame(int64 access_unit_pos, int access_unit_size,
if (!sps) if (!sps)
return false; return false;
RCHECK(UpdateVideoDecoderConfig(sps)); RCHECK(UpdateVideoDecoderConfig(sps));
decoder_config_check_pending_ = false;
}
} }
// Emit a frame. // Create the media sample, emitting always the previous sample after
DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_ // calculating its duration.
<< " size=" << access_unit_size; scoped_refptr<MediaSample> media_sample = MediaSample::CopyFrom(
int es_size; converted_frame.data(), converted_frame.size(), is_key_frame);
const uint8* es;
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<MediaSample> media_sample =
MediaSample::CopyFrom(
es,
access_unit_size,
is_key_frame);
media_sample->set_dts(current_timing_desc.dts); media_sample->set_dts(current_timing_desc.dts);
media_sample->set_pts(current_timing_desc.pts); 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; return true;
} }
bool EsParserH264::UpdateVideoDecoderConfig(const H264SPS* sps) { bool EsParserH264::UpdateVideoDecoderConfig(const H264SPS* sps) {
// TODO(tinskip): Generate an error if video configuration change is detected. std::vector<uint8> decoder_config_record;
if (!stream_converter_->GetAVCDecoderConfigurationRecord(
&decoder_config_record)) {
DLOG(ERROR) << "Failure to construct an AVCDecoderConfigurationRecord";
return false;
}
if (last_video_decoder_config_) { if (last_video_decoder_config_) {
// Varying video configurations currently not supported. Just assume that // Verify that the video decoder config has not changed.
// the video configuration has not changed. if (last_video_decoder_config_->extra_data() == decoder_config_record) {
// Video configuration has not changed.
return true; return true;
} }
NOTIMPLEMENTED() << "Varying video configurations are not supported.";
return false;
}
// TODO(damienv): a MAP unit can be either 16 or 32 pixels. // TODO(damienv): a MAP unit can be either 16 or 32 pixels.
// although it's 16 pixels for progressive non MBAFF frames. // although it's 16 pixels for progressive non MBAFF frames.
@ -299,13 +341,16 @@ bool EsParserH264::UpdateVideoDecoderConfig(const H264SPS* sps) {
kMpeg2Timescale, kMpeg2Timescale,
kInfiniteDuration, kInfiniteDuration,
kCodecH264, 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(), std::string(),
width, width,
height, height,
kCommonNaluLengthSize, H264ByteToUnitStreamConverter::kUnitStreamNaluLengthSize,
NULL, // TODO(tinskip): calculate AVCDecoderConfigurationRecord. decoder_config_record.data(),
0, decoder_config_record.size(),
false)); false));
DVLOG(1) << "Profile IDC: " << sps->profile_idc; DVLOG(1) << "Profile IDC: " << sps->profile_idc;
DVLOG(1) << "Level IDC: " << sps->level_idc; DVLOG(1) << "Level IDC: " << sps->level_idc;

View File

@ -16,6 +16,7 @@
namespace media { namespace media {
class H264ByteToUnitStreamConverter;
class H264Parser; class H264Parser;
class OffsetByteQueue; class OffsetByteQueue;
struct H264SPS; struct H264SPS;
@ -83,8 +84,16 @@ class EsParserH264 : public EsParser {
int64 current_access_unit_pos_; int64 current_access_unit_pos_;
int64 next_access_unit_pos_; int64 next_access_unit_pos_;
// Filter to convert H.264 Annex B byte stream to unit stream.
scoped_ptr<H264ByteToUnitStreamConverter> stream_converter_;
// Last video decoder config. // Last video decoder config.
scoped_refptr<StreamInfo> last_video_decoder_config_; scoped_refptr<StreamInfo> last_video_decoder_config_;
bool decoder_config_check_pending_;
// Frame for which we do not yet have a duration.
scoped_refptr<MediaSample> pending_sample_;
uint64 pending_sample_duration_;
}; };
} // namespace mp2t } // namespace mp2t

View File

@ -126,7 +126,7 @@ class Mp2tMediaParserTest : public testing::Test {
TEST_F(Mp2tMediaParserTest, UnalignedAppend17) { TEST_F(Mp2tMediaParserTest, UnalignedAppend17) {
// Test small, non-segment-aligned appends. // Test small, non-segment-aligned appends.
ParseMpeg2TsFile("bear-1280x720.ts", 17); ParseMpeg2TsFile("bear-1280x720.ts", 17);
EXPECT_EQ(video_frame_count_, 81); EXPECT_EQ(video_frame_count_, 80);
parser_->Flush(); parser_->Flush();
EXPECT_EQ(video_frame_count_, 82); EXPECT_EQ(video_frame_count_, 82);
} }
@ -134,7 +134,7 @@ TEST_F(Mp2tMediaParserTest, UnalignedAppend17) {
TEST_F(Mp2tMediaParserTest, UnalignedAppend512) { TEST_F(Mp2tMediaParserTest, UnalignedAppend512) {
// Test small, non-segment-aligned appends. // Test small, non-segment-aligned appends.
ParseMpeg2TsFile("bear-1280x720.ts", 512); ParseMpeg2TsFile("bear-1280x720.ts", 512);
EXPECT_EQ(video_frame_count_, 81); EXPECT_EQ(video_frame_count_, 80);
parser_->Flush(); parser_->Flush();
EXPECT_EQ(video_frame_count_, 82); 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 // (close to 2^33 / 90000) which results in timestamps wrap around
// in the Mpeg2 TS stream. // in the Mpeg2 TS stream.
ParseMpeg2TsFile("bear-1280x720_ptswraparound.ts", 512); 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_GE(video_min_dts_, (95443 - 1) * kMpeg2Timescale);
EXPECT_LE(video_max_dts_, EXPECT_LE(video_max_dts_,
static_cast<int64>((95443 + 4)) * kMpeg2Timescale); static_cast<int64>((95443 + 4)) * kMpeg2Timescale);

View File

@ -74,3 +74,6 @@ bear.h264:
bear.mp4 (https://chromiumcodereview.appspot.com/10805089): bear.mp4 (https://chromiumcodereview.appspot.com/10805089):
ffmpeg -i bear.mp4 -vcodec copy -vbsf h264_mp4toannexb \ ffmpeg -i bear.mp4 -vcodec copy -vbsf h264_mp4toannexb \
-an bear.h264 -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.

Binary file not shown.

Binary file not shown.