Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include <stdio.h> |
| 12 | |
| 13 | #ifdef WIN32 |
| 14 | #include <winsock2.h> |
| 15 | #endif |
| 16 | #ifdef WEBRTC_LINUX |
| 17 | #include <netinet/in.h> |
| 18 | #endif |
| 19 | |
| 20 | #include <iostream> |
| 21 | #include <map> |
| 22 | #include <string> |
| 23 | |
Karl Wiberg | 918f50c | 2018-07-05 11:40:33 +0200 | [diff] [blame] | 24 | #include "absl/memory/memory.h" |
Fredrik Solenberg | bbf21a3 | 2018-04-12 22:44:09 +0200 | [diff] [blame] | 25 | #include "api/audio/audio_frame.h" |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 26 | #include "api/audio_codecs/L16/audio_encoder_L16.h" |
| 27 | #include "api/audio_codecs/g711/audio_encoder_g711.h" |
| 28 | #include "api/audio_codecs/g722/audio_encoder_g722.h" |
| 29 | #include "api/audio_codecs/ilbc/audio_encoder_ilbc.h" |
| 30 | #include "api/audio_codecs/isac/audio_encoder_isac.h" |
| 31 | #include "api/audio_codecs/opus/audio_encoder_opus.h" |
| 32 | #include "modules/audio_coding/codecs/cng/audio_encoder_cng.h" |
| 33 | #include "modules/audio_coding/include/audio_coding_module.h" |
| 34 | #include "modules/audio_coding/neteq/tools/input_audio_file.h" |
| 35 | #include "rtc_base/flags.h" |
| 36 | #include "rtc_base/numerics/safe_conversions.h" |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 37 | |
| 38 | namespace webrtc { |
| 39 | namespace test { |
| 40 | namespace { |
| 41 | |
| 42 | // Define command line flags. |
Mirko Bonadei | 5ccdc13 | 2018-10-18 11:35:32 +0200 | [diff] [blame^] | 43 | WEBRTC_DEFINE_bool(list_codecs, false, "Enumerate all codecs"); |
| 44 | WEBRTC_DEFINE_string(codec, "opus", "Codec to use"); |
| 45 | WEBRTC_DEFINE_int(frame_len, |
| 46 | 0, |
| 47 | "Frame length in ms; 0 indicates codec default value"); |
| 48 | WEBRTC_DEFINE_int(bitrate, |
| 49 | 0, |
| 50 | "Bitrate in kbps; 0 indicates codec default value"); |
| 51 | WEBRTC_DEFINE_int(payload_type, |
| 52 | -1, |
| 53 | "RTP payload type; -1 indicates codec default value"); |
| 54 | WEBRTC_DEFINE_int(cng_payload_type, |
| 55 | -1, |
| 56 | "RTP payload type for CNG; -1 indicates default value"); |
| 57 | WEBRTC_DEFINE_int(ssrc, 0, "SSRC to write to the RTP header"); |
| 58 | WEBRTC_DEFINE_bool(dtx, false, "Use DTX/CNG"); |
| 59 | WEBRTC_DEFINE_int(sample_rate, 48000, "Sample rate of the input file"); |
| 60 | WEBRTC_DEFINE_bool(help, false, "Print this message"); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 61 | |
| 62 | // Add new codecs here, and to the map below. |
| 63 | enum class CodecType { |
| 64 | kOpus, |
| 65 | kPcmU, |
| 66 | kPcmA, |
| 67 | kG722, |
| 68 | kPcm16b8, |
| 69 | kPcm16b16, |
| 70 | kPcm16b32, |
| 71 | kPcm16b48, |
| 72 | kIlbc, |
| 73 | kIsac |
| 74 | }; |
| 75 | |
| 76 | struct CodecTypeAndInfo { |
| 77 | CodecType type; |
| 78 | int default_payload_type; |
| 79 | bool internal_dtx; |
| 80 | }; |
| 81 | |
| 82 | // List all supported codecs here. This map defines the command-line parameter |
| 83 | // value (the key string) for selecting each codec, together with information |
| 84 | // whether it is using internal or external DTX/CNG. |
| 85 | const std::map<std::string, CodecTypeAndInfo>& CodecList() { |
| 86 | static const auto* const codec_list = |
| 87 | new std::map<std::string, CodecTypeAndInfo>{ |
| 88 | {"opus", {CodecType::kOpus, 111, true}}, |
| 89 | {"pcmu", {CodecType::kPcmU, 0, false}}, |
| 90 | {"pcma", {CodecType::kPcmA, 8, false}}, |
| 91 | {"g722", {CodecType::kG722, 9, false}}, |
| 92 | {"pcm16b_8", {CodecType::kPcm16b8, 93, false}}, |
| 93 | {"pcm16b_16", {CodecType::kPcm16b16, 94, false}}, |
| 94 | {"pcm16b_32", {CodecType::kPcm16b32, 95, false}}, |
| 95 | {"pcm16b_48", {CodecType::kPcm16b48, 96, false}}, |
| 96 | {"ilbc", {CodecType::kIlbc, 102, false}}, |
| 97 | {"isac", {CodecType::kIsac, 103, false}}}; |
| 98 | return *codec_list; |
| 99 | } |
| 100 | |
| 101 | // This class will receive callbacks from ACM when a packet is ready, and write |
| 102 | // it to the output file. |
| 103 | class Packetizer : public AudioPacketizationCallback { |
| 104 | public: |
| 105 | Packetizer(FILE* out_file, uint32_t ssrc, int timestamp_rate_hz) |
| 106 | : out_file_(out_file), |
| 107 | ssrc_(ssrc), |
| 108 | timestamp_rate_hz_(timestamp_rate_hz) {} |
| 109 | |
| 110 | int32_t SendData(FrameType frame_type, |
| 111 | uint8_t payload_type, |
| 112 | uint32_t timestamp, |
| 113 | const uint8_t* payload_data, |
| 114 | size_t payload_len_bytes, |
| 115 | const RTPFragmentationHeader* fragmentation) override { |
| 116 | RTC_CHECK(!fragmentation); |
Henrik Lundin | 32f64d2 | 2017-11-28 13:05:35 +0100 | [diff] [blame] | 117 | if (payload_len_bytes == 0) { |
| 118 | return 0; |
| 119 | } |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 120 | |
| 121 | constexpr size_t kRtpHeaderLength = 12; |
| 122 | constexpr size_t kRtpDumpHeaderLength = 8; |
| 123 | const uint16_t length = htons(rtc::checked_cast<uint16_t>( |
| 124 | kRtpHeaderLength + kRtpDumpHeaderLength + payload_len_bytes)); |
| 125 | const uint16_t plen = htons( |
| 126 | rtc::checked_cast<uint16_t>(kRtpHeaderLength + payload_len_bytes)); |
| 127 | const uint32_t offset = htonl(timestamp / (timestamp_rate_hz_ / 1000)); |
| 128 | RTC_CHECK_EQ(fwrite(&length, sizeof(uint16_t), 1, out_file_), 1); |
| 129 | RTC_CHECK_EQ(fwrite(&plen, sizeof(uint16_t), 1, out_file_), 1); |
| 130 | RTC_CHECK_EQ(fwrite(&offset, sizeof(uint32_t), 1, out_file_), 1); |
| 131 | |
| 132 | const uint8_t rtp_header[] = {0x80, |
| 133 | static_cast<uint8_t>(payload_type & 0x7F), |
| 134 | static_cast<uint8_t>(sequence_number_ >> 8), |
| 135 | static_cast<uint8_t>(sequence_number_), |
| 136 | static_cast<uint8_t>(timestamp >> 24), |
| 137 | static_cast<uint8_t>(timestamp >> 16), |
| 138 | static_cast<uint8_t>(timestamp >> 8), |
| 139 | static_cast<uint8_t>(timestamp), |
| 140 | static_cast<uint8_t>(ssrc_ >> 24), |
| 141 | static_cast<uint8_t>(ssrc_ >> 16), |
| 142 | static_cast<uint8_t>(ssrc_ >> 8), |
| 143 | static_cast<uint8_t>(ssrc_)}; |
| 144 | static_assert(sizeof(rtp_header) == kRtpHeaderLength, ""); |
| 145 | RTC_CHECK_EQ( |
| 146 | fwrite(rtp_header, sizeof(uint8_t), kRtpHeaderLength, out_file_), |
| 147 | kRtpHeaderLength); |
| 148 | ++sequence_number_; // Intended to wrap on overflow. |
| 149 | |
| 150 | RTC_CHECK_EQ( |
| 151 | fwrite(payload_data, sizeof(uint8_t), payload_len_bytes, out_file_), |
| 152 | payload_len_bytes); |
| 153 | |
| 154 | return 0; |
| 155 | } |
| 156 | |
| 157 | private: |
| 158 | FILE* const out_file_; |
| 159 | const uint32_t ssrc_; |
| 160 | const int timestamp_rate_hz_; |
| 161 | uint16_t sequence_number_ = 0; |
| 162 | }; |
| 163 | |
| 164 | void SetFrameLenIfFlagIsPositive(int* config_frame_len) { |
| 165 | if (FLAG_frame_len > 0) { |
| 166 | *config_frame_len = FLAG_frame_len; |
| 167 | } |
| 168 | } |
| 169 | |
Henrik Lundin | f1061c2 | 2017-12-07 10:13:47 +0100 | [diff] [blame] | 170 | template <typename T> |
| 171 | typename T::Config GetCodecConfig() { |
| 172 | typename T::Config config; |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 173 | SetFrameLenIfFlagIsPositive(&config.frame_size_ms); |
Henrik Lundin | f1061c2 | 2017-12-07 10:13:47 +0100 | [diff] [blame] | 174 | RTC_CHECK(config.IsOk()); |
| 175 | return config; |
| 176 | } |
| 177 | |
| 178 | AudioEncoderL16::Config Pcm16bConfig(CodecType codec_type) { |
| 179 | auto config = GetCodecConfig<AudioEncoderL16>(); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 180 | switch (codec_type) { |
| 181 | case CodecType::kPcm16b8: |
| 182 | config.sample_rate_hz = 8000; |
| 183 | return config; |
| 184 | case CodecType::kPcm16b16: |
| 185 | config.sample_rate_hz = 16000; |
| 186 | return config; |
| 187 | case CodecType::kPcm16b32: |
| 188 | config.sample_rate_hz = 32000; |
| 189 | return config; |
| 190 | case CodecType::kPcm16b48: |
| 191 | config.sample_rate_hz = 48000; |
| 192 | return config; |
| 193 | default: |
| 194 | RTC_NOTREACHED(); |
| 195 | return config; |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | std::unique_ptr<AudioEncoder> CreateEncoder(CodecType codec_type, |
| 200 | int payload_type) { |
| 201 | switch (codec_type) { |
| 202 | case CodecType::kOpus: { |
Henrik Lundin | f1061c2 | 2017-12-07 10:13:47 +0100 | [diff] [blame] | 203 | AudioEncoderOpus::Config config = GetCodecConfig<AudioEncoderOpus>(); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 204 | if (FLAG_bitrate > 0) { |
| 205 | config.bitrate_bps = FLAG_bitrate; |
| 206 | } |
| 207 | config.dtx_enabled = FLAG_dtx; |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 208 | RTC_CHECK(config.IsOk()); |
| 209 | return AudioEncoderOpus::MakeAudioEncoder(config, payload_type); |
| 210 | } |
| 211 | |
| 212 | case CodecType::kPcmU: |
| 213 | case CodecType::kPcmA: { |
Henrik Lundin | f1061c2 | 2017-12-07 10:13:47 +0100 | [diff] [blame] | 214 | AudioEncoderG711::Config config = GetCodecConfig<AudioEncoderG711>(); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 215 | config.type = codec_type == CodecType::kPcmU |
| 216 | ? AudioEncoderG711::Config::Type::kPcmU |
| 217 | : AudioEncoderG711::Config::Type::kPcmA; |
| 218 | RTC_CHECK(config.IsOk()); |
| 219 | return AudioEncoderG711::MakeAudioEncoder(config, payload_type); |
| 220 | } |
| 221 | |
| 222 | case CodecType::kG722: { |
Henrik Lundin | f1061c2 | 2017-12-07 10:13:47 +0100 | [diff] [blame] | 223 | return AudioEncoderG722::MakeAudioEncoder( |
| 224 | GetCodecConfig<AudioEncoderG722>(), payload_type); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 225 | } |
| 226 | |
| 227 | case CodecType::kPcm16b8: |
| 228 | case CodecType::kPcm16b16: |
| 229 | case CodecType::kPcm16b32: |
| 230 | case CodecType::kPcm16b48: { |
| 231 | return AudioEncoderL16::MakeAudioEncoder(Pcm16bConfig(codec_type), |
| 232 | payload_type); |
| 233 | } |
| 234 | |
| 235 | case CodecType::kIlbc: { |
Henrik Lundin | f1061c2 | 2017-12-07 10:13:47 +0100 | [diff] [blame] | 236 | return AudioEncoderIlbc::MakeAudioEncoder( |
| 237 | GetCodecConfig<AudioEncoderIlbc>(), payload_type); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 238 | } |
| 239 | |
| 240 | case CodecType::kIsac: { |
Henrik Lundin | f1061c2 | 2017-12-07 10:13:47 +0100 | [diff] [blame] | 241 | return AudioEncoderIsac::MakeAudioEncoder( |
| 242 | GetCodecConfig<AudioEncoderIsac>(), payload_type); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 243 | } |
| 244 | } |
| 245 | RTC_NOTREACHED(); |
| 246 | return nullptr; |
| 247 | } |
| 248 | |
| 249 | AudioEncoderCng::Config GetCngConfig(int sample_rate_hz) { |
| 250 | AudioEncoderCng::Config cng_config; |
| 251 | const auto default_payload_type = [&] { |
| 252 | switch (sample_rate_hz) { |
Yves Gerey | 665174f | 2018-06-19 15:03:05 +0200 | [diff] [blame] | 253 | case 8000: |
| 254 | return 13; |
| 255 | case 16000: |
| 256 | return 98; |
| 257 | case 32000: |
| 258 | return 99; |
| 259 | case 48000: |
| 260 | return 100; |
| 261 | default: |
| 262 | RTC_NOTREACHED(); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 263 | } |
| 264 | return 0; |
| 265 | }; |
| 266 | cng_config.payload_type = FLAG_cng_payload_type != -1 |
| 267 | ? FLAG_cng_payload_type |
| 268 | : default_payload_type(); |
| 269 | return cng_config; |
| 270 | } |
| 271 | |
| 272 | int RunRtpEncode(int argc, char* argv[]) { |
| 273 | const std::string program_name = argv[0]; |
| 274 | const std::string usage = |
| 275 | "Tool for generating an RTP dump file from audio input.\n" |
| 276 | "Run " + |
| 277 | program_name + |
| 278 | " --help for usage.\n" |
| 279 | "Example usage:\n" + |
| 280 | program_name + " input.pcm output.rtp --codec=[codec] " + |
| 281 | "--frame_len=[frame_len] --bitrate=[bitrate]\n\n"; |
| 282 | if (rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true) || FLAG_help || |
| 283 | (!FLAG_list_codecs && argc != 3)) { |
| 284 | printf("%s", usage.c_str()); |
| 285 | if (FLAG_help) { |
| 286 | rtc::FlagList::Print(nullptr, false); |
| 287 | return 0; |
| 288 | } |
| 289 | return 1; |
| 290 | } |
| 291 | |
| 292 | if (FLAG_list_codecs) { |
| 293 | printf("The following arguments are valid --codec parameters:\n"); |
| 294 | for (const auto& c : CodecList()) { |
| 295 | printf(" %s\n", c.first.c_str()); |
| 296 | } |
| 297 | return 0; |
| 298 | } |
| 299 | |
| 300 | const auto codec_it = CodecList().find(FLAG_codec); |
| 301 | if (codec_it == CodecList().end()) { |
| 302 | printf("%s is not a valid codec name.\n", FLAG_codec); |
| 303 | printf("Use argument --list_codecs to see all valid codec names.\n"); |
| 304 | return 1; |
| 305 | } |
| 306 | |
| 307 | // Create the codec. |
| 308 | const int payload_type = FLAG_payload_type == -1 |
| 309 | ? codec_it->second.default_payload_type |
| 310 | : FLAG_payload_type; |
| 311 | std::unique_ptr<AudioEncoder> codec = |
| 312 | CreateEncoder(codec_it->second.type, payload_type); |
| 313 | |
| 314 | // Create an external VAD/CNG encoder if needed. |
Henrik Lundin | 32f64d2 | 2017-11-28 13:05:35 +0100 | [diff] [blame] | 315 | if (FLAG_dtx && !codec_it->second.internal_dtx) { |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 316 | AudioEncoderCng::Config cng_config = GetCngConfig(codec->SampleRateHz()); |
| 317 | RTC_DCHECK(codec); |
| 318 | cng_config.speech_encoder = std::move(codec); |
Karl Wiberg | 918f50c | 2018-07-05 11:40:33 +0200 | [diff] [blame] | 319 | codec = absl::make_unique<AudioEncoderCng>(std::move(cng_config)); |
Henrik Lundin | 1391bd4 | 2017-11-24 09:28:57 +0100 | [diff] [blame] | 320 | } |
| 321 | RTC_DCHECK(codec); |
| 322 | |
| 323 | // Set up ACM. |
| 324 | const int timestamp_rate_hz = codec->RtpTimestampRateHz(); |
| 325 | AudioCodingModule::Config config; |
| 326 | std::unique_ptr<AudioCodingModule> acm(AudioCodingModule::Create(config)); |
| 327 | acm->SetEncoder(std::move(codec)); |
| 328 | |
| 329 | // Open files. |
| 330 | printf("Input file: %s\n", argv[1]); |
| 331 | InputAudioFile input_file(argv[1], false); // Open input in non-looping mode. |
| 332 | FILE* out_file = fopen(argv[2], "wb"); |
| 333 | RTC_CHECK(out_file) << "Could not open file " << argv[2] << " for writing"; |
| 334 | printf("Output file: %s\n", argv[2]); |
| 335 | fprintf(out_file, "#!rtpplay1.0 \n"); //, |
| 336 | // Write 3 32-bit values followed by 2 16-bit values, all set to 0. This means |
| 337 | // a total of 16 bytes. |
| 338 | const uint8_t file_header[16] = {0}; |
| 339 | RTC_CHECK_EQ(fwrite(file_header, sizeof(file_header), 1, out_file), 1); |
| 340 | |
| 341 | // Create and register the packetizer, which will write the packets to file. |
| 342 | Packetizer packetizer(out_file, FLAG_ssrc, timestamp_rate_hz); |
| 343 | RTC_DCHECK_EQ(acm->RegisterTransportCallback(&packetizer), 0); |
| 344 | |
| 345 | AudioFrame audio_frame; |
| 346 | audio_frame.samples_per_channel_ = FLAG_sample_rate / 100; // 10 ms |
| 347 | audio_frame.sample_rate_hz_ = FLAG_sample_rate; |
| 348 | audio_frame.num_channels_ = 1; |
| 349 | |
| 350 | while (input_file.Read(audio_frame.samples_per_channel_, |
| 351 | audio_frame.mutable_data())) { |
| 352 | RTC_CHECK_GE(acm->Add10MsData(audio_frame), 0); |
| 353 | audio_frame.timestamp_ += audio_frame.samples_per_channel_; |
| 354 | } |
| 355 | |
| 356 | return 0; |
| 357 | } |
| 358 | |
| 359 | } // namespace |
| 360 | } // namespace test |
| 361 | } // namespace webrtc |
| 362 | |
| 363 | int main(int argc, char* argv[]) { |
| 364 | return webrtc::test::RunRtpEncode(argc, argv); |
| 365 | } |