Add support for writing h264 decoder input to file and parsing interleaved length/packet RTP dumps.

This is useful for debugging h264 input when we don't have an h264 decoder, as the resulting file should be possible to play back using mplayer. It is also often convenient to dump rtp packets in an interleaved format where the size of a packet is inserted before the actual payload.

R=pbos@webrtc.org

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

Cr-Commit-Position: refs/heads/master@{#8558}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8558 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
stefan@webrtc.org 2015-03-02 16:18:56 +00:00
parent 3fe17d1598
commit 48ac226b9a
4 changed files with 154 additions and 56 deletions

View File

@ -24,20 +24,20 @@ class FakeDecoder : public VideoDecoder {
FakeDecoder();
virtual ~FakeDecoder() {}
virtual int32_t InitDecode(const VideoCodec* config,
int32_t number_of_cores) OVERRIDE;
int32_t InitDecode(const VideoCodec* config,
int32_t number_of_cores) override;
virtual int32_t Decode(const EncodedImage& input,
bool missing_frames,
const RTPFragmentationHeader* fragmentation,
const CodecSpecificInfo* codec_specific_info,
int64_t render_time_ms) OVERRIDE;
int32_t Decode(const EncodedImage& input,
bool missing_frames,
const RTPFragmentationHeader* fragmentation,
const CodecSpecificInfo* codec_specific_info,
int64_t render_time_ms) override;
virtual int32_t RegisterDecodeCompleteCallback(
DecodedImageCallback* callback) OVERRIDE;
int32_t RegisterDecodeCompleteCallback(
DecodedImageCallback* callback) override;
virtual int32_t Release() OVERRIDE;
virtual int32_t Reset() OVERRIDE;
int32_t Release() override;
int32_t Reset() override;
private:
VideoCodec config_;
@ -49,11 +49,24 @@ 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;
int32_t Decode(const EncodedImage& input,
bool missing_frames,
const RTPFragmentationHeader* fragmentation,
const CodecSpecificInfo* codec_specific_info,
int64_t render_time_ms) override;
};
class FakeNullDecoder : public FakeDecoder {
public:
virtual ~FakeNullDecoder() {}
int32_t Decode(const EncodedImage& input,
bool missing_frames,
const RTPFragmentationHeader* fragmentation,
const CodecSpecificInfo* codec_specific_info,
int64_t render_time_ms) override {
return 0;
}
};
} // namespace test
} // namespace webrtc

View File

@ -43,11 +43,78 @@ static uint16_t kPacketHeaderSize = 8;
} \
} while (0)
bool ReadUint32(uint32_t* out, FILE* file) {
*out = 0;
for (size_t i = 0; i < 4; ++i) {
*out <<= 8;
uint8_t tmp;
if (fread(&tmp, 1, sizeof(uint8_t), file) != sizeof(uint8_t))
return false;
*out |= tmp;
}
return true;
}
bool ReadUint16(uint16_t* out, FILE* file) {
*out = 0;
for (size_t i = 0; i < 2; ++i) {
*out <<= 8;
uint8_t tmp;
if (fread(&tmp, 1, sizeof(uint8_t), file) != sizeof(uint8_t))
return false;
*out |= tmp;
}
return true;
}
class RtpFileReaderImpl : public RtpFileReader {
public:
virtual bool Init(const std::string& filename) = 0;
};
class InterleavedRtpFileReader : public RtpFileReaderImpl {
public:
virtual ~InterleavedRtpFileReader() {
if (file_ != NULL) {
fclose(file_);
file_ = NULL;
}
}
virtual bool Init(const std::string& filename) {
file_ = fopen(filename.c_str(), "rb");
if (file_ == NULL) {
printf("ERROR: Can't open file: %s\n", filename.c_str());
return false;
}
return true;
}
virtual bool NextPacket(RtpPacket* packet) {
assert(file_ != NULL);
packet->length = RtpPacket::kMaxPacketBufferSize;
uint32_t len = 0;
TRY(ReadUint32(&len, file_));
if (packet->length < len) {
FATAL() << "Packet is too large to fit: " << len << " bytes vs "
<< packet->length
<< " bytes allocated. Consider increasing the buffer "
"size";
}
if (fread(packet->data, 1, len, file_) != len)
return false;
packet->length = len;
packet->original_length = len;
packet->time_ms = time_ms_;
time_ms_ += 5;
return true;
}
private:
FILE* file_ = NULL;
int64_t time_ms_ = 0;
};
// Read RTP packets from file in rtpdump format, as documented at:
// http://www.cs.columbia.edu/irt/software/rtptools/
class RtpDumpReader : public RtpFileReaderImpl {
@ -92,11 +159,11 @@ class RtpDumpReader : public RtpFileReaderImpl {
uint32_t source;
uint16_t port;
uint16_t padding;
TRY(ReadUint32(&start_sec));
TRY(ReadUint32(&start_usec));
TRY(ReadUint32(&source));
TRY(ReadUint16(&port));
TRY(ReadUint16(&padding));
TRY(ReadUint32(&start_sec, file_));
TRY(ReadUint32(&start_usec, file_));
TRY(ReadUint32(&source, file_));
TRY(ReadUint16(&port, file_));
TRY(ReadUint16(&padding, file_));
return true;
}
@ -108,9 +175,9 @@ class RtpDumpReader : public RtpFileReaderImpl {
uint16_t len;
uint16_t plen;
uint32_t offset;
TRY(ReadUint16(&len));
TRY(ReadUint16(&plen));
TRY(ReadUint32(&offset));
TRY(ReadUint16(&len, file_));
TRY(ReadUint16(&plen, file_));
TRY(ReadUint32(&offset, file_));
// Use 'len' here because a 'plen' of 0 specifies rtcp.
len -= kPacketHeaderSize;
@ -131,30 +198,6 @@ class RtpDumpReader : public RtpFileReaderImpl {
}
private:
bool ReadUint32(uint32_t* out) {
*out = 0;
for (size_t i = 0; i < 4; ++i) {
*out <<= 8;
uint8_t tmp;
if (fread(&tmp, 1, sizeof(uint8_t), file_) != sizeof(uint8_t))
return false;
*out |= tmp;
}
return true;
}
bool ReadUint16(uint16_t* out) {
*out = 0;
for (size_t i = 0; i < 2; ++i) {
*out <<= 8;
uint8_t tmp;
if (fread(&tmp, 1, sizeof(uint8_t), file_) != sizeof(uint8_t))
return false;
*out |= tmp;
}
return true;
}
FILE* file_;
DISALLOW_COPY_AND_ASSIGN(RtpDumpReader);
@ -598,6 +641,9 @@ RtpFileReader* RtpFileReader::Create(FileFormat format,
case kRtpDump:
reader = new RtpDumpReader();
break;
case kLengthPacketInterleaved:
reader = new InterleavedRtpFileReader();
break;
}
if (!reader->Init(filename)) {
delete reader;

View File

@ -32,10 +32,7 @@ struct RtpPacket {
class RtpFileReader {
public:
enum FileFormat {
kPcap,
kRtpDump,
};
enum FileFormat { kPcap, kRtpDump, kLengthPacketInterleaved };
virtual ~RtpFileReader() {}
static RtpFileReader* Create(FileFormat format,

View File

@ -24,6 +24,7 @@
#include "webrtc/system_wrappers/interface/sleep.h"
#include "webrtc/test/encoder_settings.h"
#include "webrtc/test/null_transport.h"
#include "webrtc/test/fake_decoder.h"
#include "webrtc/test/rtp_file_reader.h"
#include "webrtc/test/run_loop.h"
#include "webrtc/test/run_test.h"
@ -106,6 +107,7 @@ bool ValidateInputFilenameNotEmpty(const char* flagname,
const std::string& string) {
return string != "";
}
DEFINE_string(input_file, "", "input file");
static std::string InputFile() {
return static_cast<std::string>(FLAGS_input_file);
@ -120,6 +122,11 @@ static std::string OutBase() {
return static_cast<std::string>(FLAGS_out_base);
}
DEFINE_string(decoder_bitstream_filename, "", "Decoder bitstream output file");
static std::string DecoderBitstreamFilename() {
return static_cast<std::string>(FLAGS_decoder_bitstream_filename);
}
// Flag for video codec.
DEFINE_string(codec, "VP8", "Video codec");
static std::string Codec() { return static_cast<std::string>(FLAGS_codec); }
@ -184,6 +191,22 @@ class FileRenderPassthrough : public VideoRenderer {
int last_height_;
};
class DecoderBitstreamFileWriter : public EncodedFrameObserver {
public:
explicit DecoderBitstreamFileWriter(const char* filename)
: file_(fopen(filename, "wb")) {
assert(file_ != NULL);
}
~DecoderBitstreamFileWriter() { fclose(file_); }
virtual void EncodedFrameCallback(const EncodedFrame& encoded_frame) {
fwrite(encoded_frame.data_, 1, encoded_frame.length_, file_);
}
private:
FILE* file_;
};
void RtpReplay() {
rtc::scoped_ptr<test::VideoRenderer> playback_video(
test::VideoRenderer::Create("Playback Video", 640, 480));
@ -215,8 +238,20 @@ void RtpReplay() {
VideoSendStream::Config::EncoderSettings encoder_settings;
encoder_settings.payload_name = flags::Codec();
encoder_settings.payload_type = flags::PayloadType();
VideoReceiveStream::Decoder decoder =
test::CreateMatchingDecoder(encoder_settings);
VideoReceiveStream::Decoder decoder;
rtc::scoped_ptr<DecoderBitstreamFileWriter> bitstream_writer;
if (flags::DecoderBitstreamFilename() != "") {
bitstream_writer.reset(new DecoderBitstreamFileWriter(
flags::DecoderBitstreamFilename().c_str()));
receive_config.pre_decode_callback = bitstream_writer.get();
}
decoder = test::CreateMatchingDecoder(encoder_settings);
if (flags::DecoderBitstreamFilename() != "") {
// Replace with a null decoder if we're writing the bitstream to a file
// instead.
delete decoder.decoder;
decoder.decoder = new test::FakeNullDecoder();
}
receive_config.decoders.push_back(decoder);
VideoReceiveStream* receive_stream =
@ -230,8 +265,15 @@ void RtpReplay() {
if (rtp_reader.get() == NULL) {
fprintf(stderr,
"Couldn't open input file as either a rtpdump or .pcap. Note "
"that .pcapng is not supported.\n");
return;
"that .pcapng is not supported.\nTrying to interpret the file as "
"length/packet interleaved.\n");
rtp_reader.reset(test::RtpFileReader::Create(
test::RtpFileReader::kLengthPacketInterleaved, flags::InputFile()));
if (rtp_reader.get() == NULL) {
fprintf(stderr,
"Unable to open input file with any supported format\n");
return;
}
}
}
receive_stream->Start();