1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include <iostream>
6 #include <string>
7 #include <fstream>
8 #include <vector>
9 #include <math.h>
10
11 using namespace std;
12
13 #include <MediaConduitInterface.h>
14 #include <VideoConduit.h>
15 #include "mozilla/UniquePtr.h"
16 #include "mozilla/Unused.h"
17 #include "nss.h"
18 #include "runnable_utils.h"
19 #include "signaling/src/common/EncodingConstraints.h"
20
21 #define GTEST_HAS_RTTI 0
22 #include "gtest/gtest.h"
23
24 const uint32_t SSRC = 1;
25
26 // MWC RNG of George Marsaglia
27 // taken from xiph.org
28 static int32_t Rz, Rw;
fast_rand(void)29 static inline int32_t fast_rand(void) {
30 Rz = 36969 * (Rz & 65535) + (Rz >> 16);
31 Rw = 18000 * (Rw & 65535) + (Rw >> 16);
32 return (Rz << 16) + Rw;
33 }
34
35 /**
36 * A Dummy AudioConduit Tester
37 * The test reads PCM samples of a standard test file and
38 * passws to audio-conduit for encoding, RTPfication and
39 * decoding every 10 milliseconds.
40 * This decoded samples are read-off the conduit for writing
41 * into output audio file in PCM format.
42 */
43 class AudioSendAndReceive {
44 public:
45 static const unsigned int PLAYOUT_SAMPLE_FREQUENCY; // default is 16000
46 static const unsigned int PLAYOUT_SAMPLE_LENGTH; // default is 160
47
AudioSendAndReceive()48 AudioSendAndReceive() {}
49
~AudioSendAndReceive()50 ~AudioSendAndReceive() {}
51
Init(RefPtr<mozilla::AudioSessionConduit> aSession,RefPtr<mozilla::AudioSessionConduit> aOtherSession,std::string fileIn,std::string fileOut)52 void Init(RefPtr<mozilla::AudioSessionConduit> aSession,
53 RefPtr<mozilla::AudioSessionConduit> aOtherSession,
54 std::string fileIn, std::string fileOut) {
55 mSession = aSession;
56 mOtherSession = aOtherSession;
57 iFile = fileIn;
58 oFile = fileOut;
59 }
60
61 // Kick start the test
62 void GenerateAndReadSamples();
63
64 private:
65 RefPtr<mozilla::AudioSessionConduit> mSession;
66 RefPtr<mozilla::AudioSessionConduit> mOtherSession;
67 std::string iFile;
68 std::string oFile;
69
70 int WriteWaveHeader(int rate, int channels, FILE* outFile);
71 int FinishWaveHeader(FILE* outFile);
72 void GenerateMusic(int16_t* buf, int len);
73 };
74
75 const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_FREQUENCY = 16000;
76 const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_LENGTH = 160;
77
WriteWaveHeader(int rate,int channels,FILE * outFile)78 int AudioSendAndReceive::WriteWaveHeader(int rate, int channels,
79 FILE* outFile) {
80 // Hardcoded for 16 bit samples
81 unsigned char header[] = {
82 // File header
83 0x52, 0x49, 0x46, 0x46, // 'RIFF'
84 0x00, 0x00, 0x00, 0x00, // chunk size
85 0x57, 0x41, 0x56, 0x45, // 'WAVE'
86 // fmt chunk. We always write 16-bit samples.
87 0x66, 0x6d, 0x74, 0x20, // 'fmt '
88 0x10, 0x00, 0x00, 0x00, // chunk size
89 0x01, 0x00, // WAVE_FORMAT_PCM
90 0xFF, 0xFF, // channels
91 0xFF, 0xFF, 0xFF, 0xFF, // sample rate
92 0x00, 0x00, 0x00, 0x00, // data rate
93 0xFF, 0xFF, // frame size in bytes
94 0x10, 0x00, // bits per sample
95 // data chunk
96 0x64, 0x61, 0x74, 0x61, // 'data'
97 0xFE, 0xFF, 0xFF, 0x7F // chunk size
98 };
99
100 #define set_uint16le(buffer, value) \
101 (buffer)[0] = (value)&0xff; \
102 (buffer)[1] = (value) >> 8;
103 #define set_uint32le(buffer, value) \
104 set_uint16le((buffer), (value)&0xffff); \
105 set_uint16le((buffer) + 2, (value) >> 16);
106
107 // set dynamic header fields
108 set_uint16le(header + 22, channels);
109 set_uint32le(header + 24, rate);
110 set_uint16le(header + 32, channels * 2);
111
112 size_t written = fwrite(header, 1, sizeof(header), outFile);
113 if (written != sizeof(header)) {
114 cerr << "Writing WAV header failed" << endl;
115 return -1;
116 }
117
118 return 0;
119 }
120
121 // Update the WAVE file header with the written length
FinishWaveHeader(FILE * outFile)122 int AudioSendAndReceive::FinishWaveHeader(FILE* outFile) {
123 // Measure how much data we've written
124 long end = ftell(outFile);
125 if (end < 16) {
126 cerr << "Couldn't get output file length" << endl;
127 return (end < 0) ? end : -1;
128 }
129
130 // Update the header
131 unsigned char size[4];
132 int err = fseek(outFile, 40, SEEK_SET);
133 if (err < 0) {
134 cerr << "Couldn't seek to WAV file header." << endl;
135 return err;
136 }
137 set_uint32le(size, (end - 44) & 0xffffffff);
138 size_t written = fwrite(size, 1, sizeof(size), outFile);
139 if (written != sizeof(size)) {
140 cerr << "Couldn't write data size to WAV header" << endl;
141 return -1;
142 }
143
144 // Return to the end
145 err = fseek(outFile, 0, SEEK_END);
146 if (err < 0) {
147 cerr << "Couldn't seek to WAV file end." << endl;
148 return err;
149 }
150
151 return 0;
152 }
153
154 // Code from xiph.org to generate music of predefined length
GenerateMusic(short * buf,int len)155 void AudioSendAndReceive::GenerateMusic(short* buf, int len) {
156 cerr << " Generating Input Music " << endl;
157 int32_t a1, a2, b1, b2;
158 int32_t c1, c2, d1, d2;
159 int32_t i, j;
160 a1 = b1 = a2 = b2 = 0;
161 c1 = c2 = d1 = d2 = 0;
162 j = 0;
163 for (i = 0; i < len - 1; i += 2) {
164 int32_t r;
165 int32_t v1, v2;
166 v1 = v2 =
167 (((j * ((j >> 12) ^ ((j >> 10 | j >> 12) & 26 & j >> 7))) & 128) + 128)
168 << 15;
169 r = fast_rand();
170 v1 += r & 65535;
171 v1 -= r >> 16;
172 r = fast_rand();
173 v2 += r & 65535;
174 v2 -= r >> 16;
175 b1 = v1 - a1 + ((b1 * 61 + 32) >> 6);
176 a1 = v1;
177 b2 = v2 - a2 + ((b2 * 61 + 32) >> 6);
178 a2 = v2;
179 c1 = (30 * (c1 + b1 + d1) + 32) >> 6;
180 d1 = b1;
181 c2 = (30 * (c2 + b2 + d2) + 32) >> 6;
182 d2 = b2;
183 v1 = (c1 + 128) >> 8;
184 v2 = (c2 + 128) >> 8;
185 buf[i] = v1 > 32767 ? 32767 : (v1 < -32768 ? -32768 : v1);
186 buf[i + 1] = v2 > 32767 ? 32767 : (v2 < -32768 ? -32768 : v2);
187 if (i % 6 == 0) j++;
188 }
189 cerr << "Generating Input Music Done " << endl;
190 }
191
192 // Hardcoded for 16 bit samples for now
GenerateAndReadSamples()193 void AudioSendAndReceive::GenerateAndReadSamples() {
194 auto audioInput = mozilla::MakeUnique<int16_t[]>(PLAYOUT_SAMPLE_LENGTH);
195 auto audioOutput = mozilla::MakeUnique<int16_t[]>(PLAYOUT_SAMPLE_LENGTH);
196 short* inbuf;
197 int sampleLengthDecoded = 0;
198 unsigned int SAMPLES = (PLAYOUT_SAMPLE_FREQUENCY / 100); // 10 milliseconds
199 int CHANNELS = 1; // mono audio
200 int sampleLengthInBytes = sizeof(int16_t) * PLAYOUT_SAMPLE_LENGTH;
201 // generated audio buffer
202 inbuf = (short*)moz_xmalloc(sizeof(short) * SAMPLES * CHANNELS);
203 memset(audioInput.get(), 0, sampleLengthInBytes);
204 memset(audioOutput.get(), 0, sampleLengthInBytes);
205 MOZ_ASSERT(SAMPLES <= PLAYOUT_SAMPLE_LENGTH);
206
207 FILE* inFile = fopen(iFile.c_str(), "wb+");
208 if (!inFile) {
209 cerr << "Input File Creation Failed " << endl;
210 free(inbuf);
211 return;
212 }
213
214 FILE* outFile = fopen(oFile.c_str(), "wb+");
215 if (!outFile) {
216 cerr << "Output File Creation Failed " << endl;
217 free(inbuf);
218 fclose(inFile);
219 return;
220 }
221
222 // Create input file with the music
223 WriteWaveHeader(PLAYOUT_SAMPLE_FREQUENCY, 1, inFile);
224 GenerateMusic(inbuf, SAMPLES);
225 mozilla::Unused << fwrite(inbuf, 1, SAMPLES * sizeof(inbuf[0]) * CHANNELS,
226 inFile);
227 FinishWaveHeader(inFile);
228 fclose(inFile);
229
230 WriteWaveHeader(PLAYOUT_SAMPLE_FREQUENCY, 1, outFile);
231 unsigned int numSamplesReadFromInput = 0;
232 do {
233 if (!memcpy(audioInput.get(), inbuf, sampleLengthInBytes)) {
234 free(inbuf);
235 fclose(outFile);
236 return;
237 }
238
239 numSamplesReadFromInput += PLAYOUT_SAMPLE_LENGTH;
240
241 mSession->SendAudioFrame(audioInput.get(), PLAYOUT_SAMPLE_LENGTH,
242 PLAYOUT_SAMPLE_FREQUENCY, 1, 10);
243
244 PR_Sleep(PR_MillisecondsToInterval(10));
245 mOtherSession->GetAudioFrame(audioOutput.get(), PLAYOUT_SAMPLE_FREQUENCY,
246 10, sampleLengthDecoded);
247 if (sampleLengthDecoded == 0) {
248 cerr << " Zero length Sample " << endl;
249 }
250
251 int wrote_ = fwrite(audioOutput.get(), 1, sampleLengthInBytes, outFile);
252 if (wrote_ != sampleLengthInBytes) {
253 cerr << "Couldn't Write " << sampleLengthInBytes << "bytes" << endl;
254 break;
255 }
256 } while (numSamplesReadFromInput < SAMPLES);
257
258 FinishWaveHeader(outFile);
259 free(inbuf);
260 fclose(outFile);
261 }
262
263 /**
264 * Webrtc Audio and Video External Transport Class
265 * The functions in this class will be invoked by the conduit
266 * when it has RTP/RTCP frame to transmit.
267 * For everty RTP/RTCP frame we receive, we pass it back
268 * to the conduit for eventual decoding and rendering.
269 */
270 class WebrtcMediaTransport : public mozilla::TransportInterface {
271 public:
WebrtcMediaTransport()272 WebrtcMediaTransport() : numPkts(0), mAudio(false), mVideo(false) {}
273
~WebrtcMediaTransport()274 ~WebrtcMediaTransport() {}
275
SendRtpPacket(const uint8_t * data,size_t len)276 virtual nsresult SendRtpPacket(const uint8_t* data, size_t len) {
277 ++numPkts;
278
279 if (mAudio) {
280 mOtherAudioSession->ReceivedRTPPacket(data, len, SSRC);
281 } else {
282 mOtherVideoSession->ReceivedRTPPacket(data, len, SSRC);
283 }
284 return NS_OK;
285 }
286
SendRtcpPacket(const uint8_t * data,size_t len)287 virtual nsresult SendRtcpPacket(const uint8_t* data, size_t len) {
288 if (mAudio) {
289 mOtherAudioSession->ReceivedRTCPPacket(data, len);
290 } else {
291 mOtherVideoSession->ReceivedRTCPPacket(data, len);
292 }
293 return NS_OK;
294 }
295
296 // Treat this object as Audio Transport
SetAudioSession(RefPtr<mozilla::AudioSessionConduit> aSession,RefPtr<mozilla::AudioSessionConduit> aOtherSession)297 void SetAudioSession(RefPtr<mozilla::AudioSessionConduit> aSession,
298 RefPtr<mozilla::AudioSessionConduit> aOtherSession) {
299 mAudioSession = aSession;
300 mOtherAudioSession = aOtherSession;
301 mAudio = true;
302 }
303
304 // Treat this object as Video Transport
SetVideoSession(RefPtr<mozilla::VideoSessionConduit> aSession,RefPtr<mozilla::VideoSessionConduit> aOtherSession)305 void SetVideoSession(RefPtr<mozilla::VideoSessionConduit> aSession,
306 RefPtr<mozilla::VideoSessionConduit> aOtherSession) {
307 mVideoSession = aSession;
308 mOtherVideoSession = aOtherSession;
309 mVideo = true;
310 }
311
312 private:
313 RefPtr<mozilla::AudioSessionConduit> mAudioSession;
314 RefPtr<mozilla::VideoSessionConduit> mVideoSession;
315 RefPtr<mozilla::VideoSessionConduit> mOtherVideoSession;
316 RefPtr<mozilla::AudioSessionConduit> mOtherAudioSession;
317 int numPkts;
318 bool mAudio, mVideo;
319 };
320
321 using namespace mozilla;
322
323 namespace test {
324
325 class TransportConduitTest : public ::testing::Test {
326 public:
TransportConduitTest()327 TransportConduitTest() {
328 // input and output file names
329 iAudiofilename = "input.wav";
330 oAudiofilename = "recorded.wav";
331
332 NSS_NoDB_Init(nullptr);
333 }
334
~TransportConduitTest()335 ~TransportConduitTest() {
336 mAudioSession = nullptr;
337 mAudioSession2 = nullptr;
338 mAudioTransport = nullptr;
339
340 mVideoSession = nullptr;
341 mVideoSession2 = nullptr;
342 mVideoRenderer = nullptr;
343 mVideoTransport = nullptr;
344 }
345
346 // 1. Dump audio samples to dummy external transport
TestDummyAudioAndTransport()347 void TestDummyAudioAndTransport() {
348 // get pointer to AudioSessionConduit
349 int err = 0;
350 mAudioSession = mozilla::AudioSessionConduit::Create();
351 if (!mAudioSession) {
352 ASSERT_NE(mAudioSession, (void*)nullptr);
353 }
354
355 mAudioSession2 = mozilla::AudioSessionConduit::Create();
356 if (!mAudioSession2) {
357 ASSERT_NE(mAudioSession2, (void*)nullptr);
358 }
359
360 WebrtcMediaTransport* xport = new WebrtcMediaTransport();
361 ASSERT_NE(xport, (void*)nullptr);
362 xport->SetAudioSession(mAudioSession, mAudioSession2);
363 mAudioTransport = xport;
364
365 // attach the transport to audio-conduit
366 err = mAudioSession->SetTransmitterTransport(mAudioTransport);
367 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
368 err = mAudioSession2->SetReceiverTransport(mAudioTransport);
369 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
370
371 // configure send and recv codecs on the audio-conduit
372 // mozilla::AudioCodecConfig cinst1(124, "PCMU", 8000, 80, 1, 64000, false);
373 mozilla::AudioCodecConfig cinst1(124, "opus", 48000, 960, 1, 64000, false);
374 mozilla::AudioCodecConfig cinst2(125, "L16", 16000, 320, 1, 256000, false);
375
376 std::vector<mozilla::AudioCodecConfig*> rcvCodecList;
377 rcvCodecList.push_back(&cinst1);
378 rcvCodecList.push_back(&cinst2);
379
380 err = mAudioSession->ConfigureSendMediaCodec(&cinst1);
381 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
382 err = mAudioSession->StartTransmitting();
383 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
384 err = mAudioSession->ConfigureRecvMediaCodecs(rcvCodecList);
385 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
386
387 err = mAudioSession2->ConfigureSendMediaCodec(&cinst1);
388 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
389 err = mAudioSession2->StartTransmitting();
390 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
391 err = mAudioSession2->ConfigureRecvMediaCodecs(rcvCodecList);
392 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
393
394 // start generating samples
395 audioTester.Init(mAudioSession, mAudioSession2, iAudiofilename,
396 oAudiofilename);
397 cerr << " ******************************************************** "
398 << endl;
399 cerr << " Generating Audio Samples " << endl;
400 cerr << " ******************************************************** "
401 << endl;
402 audioTester.GenerateAndReadSamples();
403 cerr << " ******************************************************** "
404 << endl;
405 cerr << " Input Audio File " << iAudiofilename << endl;
406 cerr << " Output Audio File " << oAudiofilename << endl;
407 cerr << " ******************************************************** "
408 << endl;
409 }
410
TestVideoConduitCodecAPI()411 void TestVideoConduitCodecAPI() {
412 int err = 0;
413 RefPtr<mozilla::VideoSessionConduit> videoSession;
414 // get pointer to VideoSessionConduit
415 videoSession = VideoSessionConduit::Create(WebRtcCallWrapper::Create());
416 if (!videoSession) {
417 ASSERT_NE(videoSession, (void*)nullptr);
418 }
419
420 std::vector<unsigned int> ssrcs = {SSRC};
421 videoSession->SetLocalSSRCs(ssrcs);
422
423 // Test Configure Recv Codec APIS
424 cerr << " *************************************************" << endl;
425 cerr << " Test Receive Codec Configuration API Now " << endl;
426 cerr << " *************************************************" << endl;
427
428 std::vector<mozilla::VideoCodecConfig*> rcvCodecList;
429
430 // Same APIs
431 mozilla::EncodingConstraints constraints;
432 mozilla::VideoCodecConfig cinst1(120, "VP8", constraints);
433 VideoCodecConfig::SimulcastEncoding encoding;
434 cinst1.mSimulcastEncodings.push_back(encoding);
435
436 // This test is disabled because with the current code this will trigger an
437 // assertion in video_receive_stream.cc because we this results in a
438 // duplicate payload type for different encoders.
439 /*
440 cerr << " *************************************************" << endl;
441 cerr << " 1. Same Codec (VP8) Repeated Twice " << endl;
442 cerr << " *************************************************" << endl;
443
444 mozilla::VideoCodecConfig cinst2(120, "VP8", constraints);
445 cinst2.mSimulcastEncodings.push_back(encoding);
446 rcvCodecList.push_back(&cinst1);
447 rcvCodecList.push_back(&cinst2);
448 err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
449 EXPECT_EQ(err, mozilla::kMediaConduitNoError);
450 rcvCodecList.pop_back();
451 rcvCodecList.pop_back();
452 */
453
454 cerr << " *************************************************" << endl;
455 cerr << " 2. Codec With Invalid Payload Names " << endl;
456 cerr << " *************************************************" << endl;
457 cerr << " Setting payload 1 with name: "
458 "I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676"
459 << endl;
460 cerr << " Setting payload 2 with name of zero length" << endl;
461
462 mozilla::VideoCodecConfig cinst3(
463 124, "I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676",
464 constraints);
465 cinst3.mSimulcastEncodings.push_back(encoding);
466 mozilla::VideoCodecConfig cinst4(124, "", constraints);
467 cinst4.mSimulcastEncodings.push_back(encoding);
468
469 rcvCodecList.push_back(&cinst3);
470 rcvCodecList.push_back(&cinst4);
471
472 err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
473 EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
474 rcvCodecList.pop_back();
475 rcvCodecList.pop_back();
476
477 cerr << " *************************************************" << endl;
478 cerr << " 3. Null Codec Parameter " << endl;
479 cerr << " *************************************************" << endl;
480
481 rcvCodecList.push_back(nullptr);
482
483 err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
484 EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
485 rcvCodecList.pop_back();
486
487 cerr << " *************************************************" << endl;
488 cerr << " Test Send Codec Configuration API Now " << endl;
489 cerr << " *************************************************" << endl;
490
491 cerr << " *************************************************" << endl;
492 cerr << " 1. Same Codec (VP8) Repeated Twice " << endl;
493 cerr << " *************************************************" << endl;
494
495 err = videoSession->ConfigureSendMediaCodec(&cinst1);
496 EXPECT_EQ(mozilla::kMediaConduitNoError, err);
497 err = videoSession->StartTransmitting();
498 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
499 err = videoSession->ConfigureSendMediaCodec(&cinst1);
500 EXPECT_EQ(mozilla::kMediaConduitNoError, err);
501 err = videoSession->StartTransmitting();
502 ASSERT_EQ(mozilla::kMediaConduitNoError, err);
503
504 cerr << " *************************************************" << endl;
505 cerr << " 2. Codec With Invalid Payload Names " << endl;
506 cerr << " *************************************************" << endl;
507 cerr << " Setting payload with name: "
508 "I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676"
509 << endl;
510
511 err = videoSession->ConfigureSendMediaCodec(&cinst3);
512 EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
513
514 cerr << " *************************************************" << endl;
515 cerr << " 3. Null Codec Parameter " << endl;
516 cerr << " *************************************************" << endl;
517
518 err = videoSession->ConfigureSendMediaCodec(nullptr);
519 EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
520
521 videoSession->DeleteStreams();
522 }
523
524 private:
525 // Audio Conduit Test Objects
526 RefPtr<mozilla::AudioSessionConduit> mAudioSession;
527 RefPtr<mozilla::AudioSessionConduit> mAudioSession2;
528 RefPtr<mozilla::TransportInterface> mAudioTransport;
529 AudioSendAndReceive audioTester;
530
531 // Video Conduit Test Objects
532 RefPtr<mozilla::VideoSessionConduit> mVideoSession;
533 RefPtr<mozilla::VideoSessionConduit> mVideoSession2;
534 RefPtr<mozilla::VideoRenderer> mVideoRenderer;
535 RefPtr<mozilla::TransportInterface> mVideoTransport;
536
537 std::string fileToPlay;
538 std::string fileToRecord;
539 std::string iAudiofilename;
540 std::string oAudiofilename;
541 };
542
543 // Disabled, see Bug 1319121
TEST_F(TransportConduitTest,DISABLED_TestDummyAudioWithTransport)544 TEST_F(TransportConduitTest, DISABLED_TestDummyAudioWithTransport) {
545 TestDummyAudioAndTransport();
546 }
547
TEST_F(TransportConduitTest,TestVideoConduitCodecAPI)548 TEST_F(TransportConduitTest, TestVideoConduitCodecAPI) {
549 TestVideoConduitCodecAPI();
550 }
551
552 } // namespace test
553