webrtc/modules/audio_coding/neteq/tools/rtp_encode.cc
Niels Möller 87e2d785a0 Prepare for splitting FrameType into AudioFrameType and VideoFrameType
This cl deprecates the FrameType enum, and adds aliases AudioFrameType
and VideoFrameType.

After downstream usage is updated, the enums will be separated
and be moved out of common_types.h.

Bug: webrtc:6883
Change-Id: I2aaf660169da45f22574b4cbb16aea8522cc07a6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/123184
Commit-Queue: Niels Moller <nisse@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27011}
2019-03-07 10:12:57 +00:00

365 lines
12 KiB
C++

/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <stdio.h>
#ifdef WIN32
#include <winsock2.h>
#endif
#ifdef WEBRTC_LINUX
#include <netinet/in.h>
#endif
#include <iostream>
#include <map>
#include <string>
#include "absl/memory/memory.h"
#include "api/audio/audio_frame.h"
#include "api/audio_codecs/L16/audio_encoder_L16.h"
#include "api/audio_codecs/g711/audio_encoder_g711.h"
#include "api/audio_codecs/g722/audio_encoder_g722.h"
#include "api/audio_codecs/ilbc/audio_encoder_ilbc.h"
#include "api/audio_codecs/isac/audio_encoder_isac.h"
#include "api/audio_codecs/opus/audio_encoder_opus.h"
#include "modules/audio_coding/codecs/cng/audio_encoder_cng.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "modules/audio_coding/neteq/tools/input_audio_file.h"
#include "rtc_base/flags.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
namespace test {
namespace {
// Define command line flags.
WEBRTC_DEFINE_bool(list_codecs, false, "Enumerate all codecs");
WEBRTC_DEFINE_string(codec, "opus", "Codec to use");
WEBRTC_DEFINE_int(frame_len,
0,
"Frame length in ms; 0 indicates codec default value");
WEBRTC_DEFINE_int(bitrate,
0,
"Bitrate in kbps; 0 indicates codec default value");
WEBRTC_DEFINE_int(payload_type,
-1,
"RTP payload type; -1 indicates codec default value");
WEBRTC_DEFINE_int(cng_payload_type,
-1,
"RTP payload type for CNG; -1 indicates default value");
WEBRTC_DEFINE_int(ssrc, 0, "SSRC to write to the RTP header");
WEBRTC_DEFINE_bool(dtx, false, "Use DTX/CNG");
WEBRTC_DEFINE_int(sample_rate, 48000, "Sample rate of the input file");
WEBRTC_DEFINE_bool(help, false, "Print this message");
// Add new codecs here, and to the map below.
enum class CodecType {
kOpus,
kPcmU,
kPcmA,
kG722,
kPcm16b8,
kPcm16b16,
kPcm16b32,
kPcm16b48,
kIlbc,
kIsac
};
struct CodecTypeAndInfo {
CodecType type;
int default_payload_type;
bool internal_dtx;
};
// List all supported codecs here. This map defines the command-line parameter
// value (the key string) for selecting each codec, together with information
// whether it is using internal or external DTX/CNG.
const std::map<std::string, CodecTypeAndInfo>& CodecList() {
static const auto* const codec_list =
new std::map<std::string, CodecTypeAndInfo>{
{"opus", {CodecType::kOpus, 111, true}},
{"pcmu", {CodecType::kPcmU, 0, false}},
{"pcma", {CodecType::kPcmA, 8, false}},
{"g722", {CodecType::kG722, 9, false}},
{"pcm16b_8", {CodecType::kPcm16b8, 93, false}},
{"pcm16b_16", {CodecType::kPcm16b16, 94, false}},
{"pcm16b_32", {CodecType::kPcm16b32, 95, false}},
{"pcm16b_48", {CodecType::kPcm16b48, 96, false}},
{"ilbc", {CodecType::kIlbc, 102, false}},
{"isac", {CodecType::kIsac, 103, false}}};
return *codec_list;
}
// This class will receive callbacks from ACM when a packet is ready, and write
// it to the output file.
class Packetizer : public AudioPacketizationCallback {
public:
Packetizer(FILE* out_file, uint32_t ssrc, int timestamp_rate_hz)
: out_file_(out_file),
ssrc_(ssrc),
timestamp_rate_hz_(timestamp_rate_hz) {}
int32_t SendData(AudioFrameType frame_type,
uint8_t payload_type,
uint32_t timestamp,
const uint8_t* payload_data,
size_t payload_len_bytes,
const RTPFragmentationHeader* fragmentation) override {
RTC_CHECK(!fragmentation);
if (payload_len_bytes == 0) {
return 0;
}
constexpr size_t kRtpHeaderLength = 12;
constexpr size_t kRtpDumpHeaderLength = 8;
const uint16_t length = htons(rtc::checked_cast<uint16_t>(
kRtpHeaderLength + kRtpDumpHeaderLength + payload_len_bytes));
const uint16_t plen = htons(
rtc::checked_cast<uint16_t>(kRtpHeaderLength + payload_len_bytes));
const uint32_t offset = htonl(timestamp / (timestamp_rate_hz_ / 1000));
RTC_CHECK_EQ(fwrite(&length, sizeof(uint16_t), 1, out_file_), 1);
RTC_CHECK_EQ(fwrite(&plen, sizeof(uint16_t), 1, out_file_), 1);
RTC_CHECK_EQ(fwrite(&offset, sizeof(uint32_t), 1, out_file_), 1);
const uint8_t rtp_header[] = {0x80,
static_cast<uint8_t>(payload_type & 0x7F),
static_cast<uint8_t>(sequence_number_ >> 8),
static_cast<uint8_t>(sequence_number_),
static_cast<uint8_t>(timestamp >> 24),
static_cast<uint8_t>(timestamp >> 16),
static_cast<uint8_t>(timestamp >> 8),
static_cast<uint8_t>(timestamp),
static_cast<uint8_t>(ssrc_ >> 24),
static_cast<uint8_t>(ssrc_ >> 16),
static_cast<uint8_t>(ssrc_ >> 8),
static_cast<uint8_t>(ssrc_)};
static_assert(sizeof(rtp_header) == kRtpHeaderLength, "");
RTC_CHECK_EQ(
fwrite(rtp_header, sizeof(uint8_t), kRtpHeaderLength, out_file_),
kRtpHeaderLength);
++sequence_number_; // Intended to wrap on overflow.
RTC_CHECK_EQ(
fwrite(payload_data, sizeof(uint8_t), payload_len_bytes, out_file_),
payload_len_bytes);
return 0;
}
private:
FILE* const out_file_;
const uint32_t ssrc_;
const int timestamp_rate_hz_;
uint16_t sequence_number_ = 0;
};
void SetFrameLenIfFlagIsPositive(int* config_frame_len) {
if (FLAG_frame_len > 0) {
*config_frame_len = FLAG_frame_len;
}
}
template <typename T>
typename T::Config GetCodecConfig() {
typename T::Config config;
SetFrameLenIfFlagIsPositive(&config.frame_size_ms);
RTC_CHECK(config.IsOk());
return config;
}
AudioEncoderL16::Config Pcm16bConfig(CodecType codec_type) {
auto config = GetCodecConfig<AudioEncoderL16>();
switch (codec_type) {
case CodecType::kPcm16b8:
config.sample_rate_hz = 8000;
return config;
case CodecType::kPcm16b16:
config.sample_rate_hz = 16000;
return config;
case CodecType::kPcm16b32:
config.sample_rate_hz = 32000;
return config;
case CodecType::kPcm16b48:
config.sample_rate_hz = 48000;
return config;
default:
RTC_NOTREACHED();
return config;
}
}
std::unique_ptr<AudioEncoder> CreateEncoder(CodecType codec_type,
int payload_type) {
switch (codec_type) {
case CodecType::kOpus: {
AudioEncoderOpus::Config config = GetCodecConfig<AudioEncoderOpus>();
if (FLAG_bitrate > 0) {
config.bitrate_bps = FLAG_bitrate;
}
config.dtx_enabled = FLAG_dtx;
RTC_CHECK(config.IsOk());
return AudioEncoderOpus::MakeAudioEncoder(config, payload_type);
}
case CodecType::kPcmU:
case CodecType::kPcmA: {
AudioEncoderG711::Config config = GetCodecConfig<AudioEncoderG711>();
config.type = codec_type == CodecType::kPcmU
? AudioEncoderG711::Config::Type::kPcmU
: AudioEncoderG711::Config::Type::kPcmA;
RTC_CHECK(config.IsOk());
return AudioEncoderG711::MakeAudioEncoder(config, payload_type);
}
case CodecType::kG722: {
return AudioEncoderG722::MakeAudioEncoder(
GetCodecConfig<AudioEncoderG722>(), payload_type);
}
case CodecType::kPcm16b8:
case CodecType::kPcm16b16:
case CodecType::kPcm16b32:
case CodecType::kPcm16b48: {
return AudioEncoderL16::MakeAudioEncoder(Pcm16bConfig(codec_type),
payload_type);
}
case CodecType::kIlbc: {
return AudioEncoderIlbc::MakeAudioEncoder(
GetCodecConfig<AudioEncoderIlbc>(), payload_type);
}
case CodecType::kIsac: {
return AudioEncoderIsac::MakeAudioEncoder(
GetCodecConfig<AudioEncoderIsac>(), payload_type);
}
}
RTC_NOTREACHED();
return nullptr;
}
AudioEncoderCngConfig GetCngConfig(int sample_rate_hz) {
AudioEncoderCngConfig cng_config;
const auto default_payload_type = [&] {
switch (sample_rate_hz) {
case 8000:
return 13;
case 16000:
return 98;
case 32000:
return 99;
case 48000:
return 100;
default:
RTC_NOTREACHED();
}
return 0;
};
cng_config.payload_type = FLAG_cng_payload_type != -1
? FLAG_cng_payload_type
: default_payload_type();
return cng_config;
}
int RunRtpEncode(int argc, char* argv[]) {
const std::string program_name = argv[0];
const std::string usage =
"Tool for generating an RTP dump file from audio input.\n"
"Run " +
program_name +
" --help for usage.\n"
"Example usage:\n" +
program_name + " input.pcm output.rtp --codec=[codec] " +
"--frame_len=[frame_len] --bitrate=[bitrate]\n\n";
if (rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true) || FLAG_help ||
(!FLAG_list_codecs && argc != 3)) {
printf("%s", usage.c_str());
if (FLAG_help) {
rtc::FlagList::Print(nullptr, false);
return 0;
}
return 1;
}
if (FLAG_list_codecs) {
printf("The following arguments are valid --codec parameters:\n");
for (const auto& c : CodecList()) {
printf(" %s\n", c.first.c_str());
}
return 0;
}
const auto codec_it = CodecList().find(FLAG_codec);
if (codec_it == CodecList().end()) {
printf("%s is not a valid codec name.\n", FLAG_codec);
printf("Use argument --list_codecs to see all valid codec names.\n");
return 1;
}
// Create the codec.
const int payload_type = FLAG_payload_type == -1
? codec_it->second.default_payload_type
: FLAG_payload_type;
std::unique_ptr<AudioEncoder> codec =
CreateEncoder(codec_it->second.type, payload_type);
// Create an external VAD/CNG encoder if needed.
if (FLAG_dtx && !codec_it->second.internal_dtx) {
AudioEncoderCngConfig cng_config = GetCngConfig(codec->SampleRateHz());
RTC_DCHECK(codec);
cng_config.speech_encoder = std::move(codec);
codec = CreateComfortNoiseEncoder(std::move(cng_config));
}
RTC_DCHECK(codec);
// Set up ACM.
const int timestamp_rate_hz = codec->RtpTimestampRateHz();
AudioCodingModule::Config config;
std::unique_ptr<AudioCodingModule> acm(AudioCodingModule::Create(config));
acm->SetEncoder(std::move(codec));
// Open files.
printf("Input file: %s\n", argv[1]);
InputAudioFile input_file(argv[1], false); // Open input in non-looping mode.
FILE* out_file = fopen(argv[2], "wb");
RTC_CHECK(out_file) << "Could not open file " << argv[2] << " for writing";
printf("Output file: %s\n", argv[2]);
fprintf(out_file, "#!rtpplay1.0 \n"); //,
// Write 3 32-bit values followed by 2 16-bit values, all set to 0. This means
// a total of 16 bytes.
const uint8_t file_header[16] = {0};
RTC_CHECK_EQ(fwrite(file_header, sizeof(file_header), 1, out_file), 1);
// Create and register the packetizer, which will write the packets to file.
Packetizer packetizer(out_file, FLAG_ssrc, timestamp_rate_hz);
RTC_DCHECK_EQ(acm->RegisterTransportCallback(&packetizer), 0);
AudioFrame audio_frame;
audio_frame.samples_per_channel_ = FLAG_sample_rate / 100; // 10 ms
audio_frame.sample_rate_hz_ = FLAG_sample_rate;
audio_frame.num_channels_ = 1;
while (input_file.Read(audio_frame.samples_per_channel_,
audio_frame.mutable_data())) {
RTC_CHECK_GE(acm->Add10MsData(audio_frame), 0);
audio_frame.timestamp_ += audio_frame.samples_per_channel_;
}
return 0;
}
} // namespace
} // namespace test
} // namespace webrtc
int main(int argc, char* argv[]) {
return webrtc::test::RunRtpEncode(argc, argv);
}