diff --git a/webrtc/test/fake_decoder.cc b/webrtc/test/fake_decoder.cc index 1624ea014..63316e0da 100644 --- a/webrtc/test/fake_decoder.cc +++ b/webrtc/test/fake_decoder.cc @@ -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 diff --git a/webrtc/test/fake_decoder.h b/webrtc/test/fake_decoder.h index 10559528f..5a03825b0 100644 --- a/webrtc/test/fake_decoder.h +++ b/webrtc/test/fake_decoder.h @@ -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 diff --git a/webrtc/test/fake_encoder.cc b/webrtc/test/fake_encoder.cc index 4ff81a18f..ecd3dd871 100644 --- a/webrtc/test/fake_encoder.cc +++ b/webrtc/test/fake_encoder.cc @@ -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 diff --git a/webrtc/test/fake_encoder.h b/webrtc/test/fake_encoder.h index 2a444a10e..3c6f73502 100644 --- a/webrtc/test/fake_encoder.h +++ b/webrtc/test/fake_encoder.h @@ -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 diff --git a/webrtc/video/end_to_end_tests.cc b/webrtc/video/end_to_end_tests.cc index 8b64f14d7..44c41b1d7 100644 --- a/webrtc/video/end_to_end_tests.cc +++ b/webrtc/video/end_to_end_tests.cc @@ -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* receive_configs, + std::vector* 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: diff --git a/webrtc/video/video_send_stream.cc b/webrtc/video/video_send_stream.cc index 4e6b2ba89..6597029a9 100644 --- a/webrtc/video/video_send_stream.cc +++ b/webrtc/video/video_send_stream.cc @@ -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) {