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