Add end-to-end H.264 packetization test.

Also correctly wires up H.264 packetization in the new Call api.

R=pbos@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/20079004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6835 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
stefan@webrtc.org 2014-08-06 09:24:53 +00:00
parent e415864a32
commit 79c3359e67
6 changed files with 173 additions and 3 deletions

View File

@ -57,5 +57,31 @@ int32_t FakeDecoder::Reset() {
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t FakeH264Decoder::Decode(const EncodedImage& input,
bool missing_frames,
const RTPFragmentationHeader* fragmentation,
const CodecSpecificInfo* codec_specific_info,
int64_t render_time_ms) {
uint8_t value = 0;
for (size_t i = 0; i < input._length; ++i) {
uint8_t kStartCode[] = {0, 0, 0, 1};
if (i < input._length - sizeof(kStartCode) &&
!memcmp(&input._buffer[i], kStartCode, sizeof(kStartCode))) {
i += sizeof(kStartCode) + 1; // Skip start code and NAL header.
}
if (input._buffer[i] != value) {
EXPECT_EQ(value, input._buffer[i])
<< "Bitstream mismatch between sender and receiver.";
return -1;
}
++value;
}
return FakeDecoder::Decode(input,
missing_frames,
fragmentation,
codec_specific_info,
render_time_ms);
}
} // namespace test
} // namespace webrtc

View File

@ -22,6 +22,7 @@ namespace test {
class FakeDecoder : public VideoDecoder {
public:
FakeDecoder();
virtual ~FakeDecoder() {}
virtual int32_t InitDecode(const VideoCodec* config,
int32_t number_of_cores) OVERRIDE;
@ -43,6 +44,17 @@ class FakeDecoder : public VideoDecoder {
I420VideoFrame frame_;
DecodedImageCallback* callback_;
};
class FakeH264Decoder : public FakeDecoder {
public:
virtual ~FakeH264Decoder() {}
virtual int32_t Decode(const EncodedImage& input,
bool missing_frames,
const RTPFragmentationHeader* fragmentation,
const CodecSpecificInfo* codec_specific_info,
int64_t render_time_ms) OVERRIDE;
};
} // namespace test
} // namespace webrtc

View File

@ -117,5 +117,60 @@ int32_t FakeEncoder::SetRates(uint32_t new_target_bitrate, uint32_t framerate) {
return 0;
}
FakeH264Encoder::FakeH264Encoder(Clock* clock)
: FakeEncoder(clock), callback_(NULL), idr_counter_(0) {
FakeEncoder::RegisterEncodeCompleteCallback(this);
}
int32_t FakeH264Encoder::RegisterEncodeCompleteCallback(
EncodedImageCallback* callback) {
callback_ = callback;
return 0;
}
int32_t FakeH264Encoder::Encoded(EncodedImage& encoded_image,
const CodecSpecificInfo* codec_specific_info,
const RTPFragmentationHeader* fragments) {
const size_t kSpsSize = 8;
const size_t kPpsSize = 11;
const int kIdrFrequency = 10;
RTPFragmentationHeader fragmentation;
if (idr_counter_++ % kIdrFrequency == 0 &&
encoded_image._length > kSpsSize + kPpsSize + 1) {
const size_t kNumSlices = 3;
fragmentation.VerifyAndAllocateFragmentationHeader(kNumSlices);
fragmentation.fragmentationOffset[0] = 0;
fragmentation.fragmentationLength[0] = kSpsSize;
fragmentation.fragmentationOffset[1] = kSpsSize;
fragmentation.fragmentationLength[1] = kPpsSize;
fragmentation.fragmentationOffset[2] = kSpsSize + kPpsSize;
fragmentation.fragmentationLength[2] =
encoded_image._length - (kSpsSize + kPpsSize);
const uint8_t kSpsNalHeader = 0x37;
const uint8_t kPpsNalHeader = 0x38;
const uint8_t kIdrNalHeader = 0x15;
encoded_image._buffer[fragmentation.fragmentationOffset[0]] = kSpsNalHeader;
encoded_image._buffer[fragmentation.fragmentationOffset[1]] = kPpsNalHeader;
encoded_image._buffer[fragmentation.fragmentationOffset[2]] = kIdrNalHeader;
} else {
const size_t kNumSlices = 1;
fragmentation.VerifyAndAllocateFragmentationHeader(kNumSlices);
fragmentation.fragmentationOffset[0] = 0;
fragmentation.fragmentationLength[0] = encoded_image._length;
const uint8_t kNalHeader = 0x11;
encoded_image._buffer[fragmentation.fragmentationOffset[0]] = kNalHeader;
}
uint8_t value = 0;
int fragment_counter = 0;
for (size_t i = 0; i < encoded_image._length; ++i) {
if (fragment_counter == fragmentation.fragmentationVectorSize ||
i != fragmentation.fragmentationOffset[fragment_counter]) {
encoded_image._buffer[i] = value++;
} else {
++fragment_counter;
}
}
return callback_->Encoded(encoded_image, NULL, &fragmentation);
}
} // namespace test
} // namespace webrtc

View File

@ -50,6 +50,24 @@ class FakeEncoder : public VideoEncoder {
int64_t last_encode_time_ms_;
uint8_t encoded_buffer_[100000];
};
class FakeH264Encoder : public FakeEncoder, public EncodedImageCallback {
public:
explicit FakeH264Encoder(Clock* clock);
virtual ~FakeH264Encoder() {}
virtual int32_t RegisterEncodeCompleteCallback(
EncodedImageCallback* callback) OVERRIDE;
virtual int32_t Encoded(
EncodedImage& encodedImage,
const CodecSpecificInfo* codecSpecificInfo = NULL,
const RTPFragmentationHeader* fragments = NULL) OVERRIDE;
private:
EncodedImageCallback* callback_;
int idr_counter_;
};
} // namespace test
} // namespace webrtc

View File

@ -209,6 +209,57 @@ TEST_F(EndToEndTest, TransmitsFirstFrame) {
DestroyStreams();
}
TEST_F(EndToEndTest, SendsAndReceivesH264) {
class H264Observer : public test::EndToEndTest, public VideoRenderer {
public:
H264Observer()
: EndToEndTest(2 * kDefaultTimeoutMs),
fake_encoder_(Clock::GetRealTimeClock()),
frame_counter_(0) {}
virtual void PerformTest() OVERRIDE {
EXPECT_EQ(kEventSignaled, Wait())
<< "Timed out while waiting for enough frames to be decoded.";
}
virtual void ModifyConfigs(
VideoSendStream::Config* send_config,
std::vector<VideoReceiveStream::Config>* receive_configs,
std::vector<VideoStream>* video_streams) {
send_config->encoder_settings.encoder = &fake_encoder_;
send_config->encoder_settings.payload_name = "H264";
send_config->encoder_settings.payload_type = kFakeSendPayloadType;
(*video_streams)[0].min_bitrate_bps = 50000;
(*video_streams)[0].target_bitrate_bps =
(*video_streams)[0].max_bitrate_bps = 2000000;
(*receive_configs)[0].renderer = this;
VideoCodec codec =
test::CreateDecoderVideoCodec(send_config->encoder_settings);
(*receive_configs)[0].codecs.resize(1);
(*receive_configs)[0].codecs[0] = codec;
(*receive_configs)[0].external_decoders.resize(1);
(*receive_configs)[0].external_decoders[0].payload_type =
send_config->encoder_settings.payload_type;
(*receive_configs)[0].external_decoders[0].decoder = &fake_decoder_;
}
virtual void RenderFrame(const I420VideoFrame& video_frame,
int time_to_render_ms) OVERRIDE {
const int kRequiredFrames = 500;
if (++frame_counter_ == kRequiredFrames)
observation_complete_->Set();
}
private:
test::FakeH264Decoder fake_decoder_;
test::FakeH264Encoder fake_encoder_;
int frame_counter_;
} test;
RunBaseTest(&test);
}
TEST_F(EndToEndTest, ReceiverUsesLocalSsrc) {
class SyncRtcpObserver : public test::EndToEndTest {
public:

View File

@ -304,9 +304,13 @@ bool VideoSendStream::ReconfigureVideoEncoder(
VideoCodec video_codec;
memset(&video_codec, 0, sizeof(video_codec));
video_codec.codecType =
(config_.encoder_settings.payload_name == "VP8" ? kVideoCodecVP8
: kVideoCodecGeneric);
if (config_.encoder_settings.payload_name == "VP8") {
video_codec.codecType = kVideoCodecVP8;
} else if (config_.encoder_settings.payload_name == "H264") {
video_codec.codecType = kVideoCodecH264;
} else {
video_codec.codecType = kVideoCodecGeneric;
}
if (video_codec.codecType == kVideoCodecVP8) {
video_codec.codecSpecific.VP8.resilience = kResilientStream;
@ -316,6 +320,10 @@ bool VideoSendStream::ReconfigureVideoEncoder(
video_codec.codecSpecific.VP8.automaticResizeOn = false;
video_codec.codecSpecific.VP8.frameDroppingOn = true;
video_codec.codecSpecific.VP8.keyFrameInterval = 3000;
} else if (video_codec.codecType == kVideoCodecH264) {
video_codec.codecSpecific.H264.profile = kProfileBase;
video_codec.codecSpecific.H264.frameDroppingOn = true;
video_codec.codecSpecific.H264.keyFrameInterval = 3000;
}
if (video_codec.codecType == kVideoCodecVP8) {