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