• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2012 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 "webrtc/modules/utility/source/file_player_impl.h"
12 #include "webrtc/system_wrappers/interface/logging.h"
13 
14 #ifdef WEBRTC_MODULE_UTILITY_VIDEO
15     #include "frame_scaler.h"
16     #include "tick_util.h"
17     #include "video_coder.h"
18 #endif
19 
20 namespace webrtc {
CreateFilePlayer(uint32_t instanceID,FileFormats fileFormat)21 FilePlayer* FilePlayer::CreateFilePlayer(uint32_t instanceID,
22                                          FileFormats fileFormat)
23 {
24     switch(fileFormat)
25     {
26     case kFileFormatWavFile:
27     case kFileFormatCompressedFile:
28     case kFileFormatPreencodedFile:
29     case kFileFormatPcm16kHzFile:
30     case kFileFormatPcm8kHzFile:
31     case kFileFormatPcm32kHzFile:
32         // audio formats
33         return new FilePlayerImpl(instanceID, fileFormat);
34     case kFileFormatAviFile:
35 #ifdef WEBRTC_MODULE_UTILITY_VIDEO
36         return new VideoFilePlayerImpl(instanceID, fileFormat);
37 #else
38         assert(false);
39         return NULL;
40 #endif
41     }
42     assert(false);
43     return NULL;
44 }
45 
DestroyFilePlayer(FilePlayer * player)46 void FilePlayer::DestroyFilePlayer(FilePlayer* player)
47 {
48     delete player;
49 }
50 
FilePlayerImpl(const uint32_t instanceID,const FileFormats fileFormat)51 FilePlayerImpl::FilePlayerImpl(const uint32_t instanceID,
52                                const FileFormats fileFormat)
53     : _instanceID(instanceID),
54       _fileFormat(fileFormat),
55       _fileModule(*MediaFile::CreateMediaFile(instanceID)),
56       _decodedLengthInMS(0),
57       _audioDecoder(instanceID),
58       _codec(),
59       _numberOf10MsPerFrame(0),
60       _numberOf10MsInDecoder(0),
61       _resampler(),
62       _scaling(1.0)
63 {
64     _codec.plfreq = 0;
65 }
66 
~FilePlayerImpl()67 FilePlayerImpl::~FilePlayerImpl()
68 {
69     MediaFile::DestroyMediaFile(&_fileModule);
70 }
71 
Frequency() const72 int32_t FilePlayerImpl::Frequency() const
73 {
74     if(_codec.plfreq == 0)
75     {
76         return -1;
77     }
78     // Make sure that sample rate is 8,16 or 32 kHz. E.g. WAVE files may have
79     // other sampling rates.
80     if(_codec.plfreq == 11000)
81     {
82         return 16000;
83     }
84     else if(_codec.plfreq == 22000)
85     {
86         return 32000;
87     }
88     else if(_codec.plfreq == 44000)
89     {
90         return 32000;
91     }
92     else if(_codec.plfreq == 48000)
93     {
94         return 32000;
95     }
96     else
97     {
98         return _codec.plfreq;
99     }
100 }
101 
AudioCodec(CodecInst & audioCodec) const102 int32_t FilePlayerImpl::AudioCodec(CodecInst& audioCodec) const
103 {
104     audioCodec = _codec;
105     return 0;
106 }
107 
Get10msAudioFromFile(int16_t * outBuffer,int & lengthInSamples,int frequencyInHz)108 int32_t FilePlayerImpl::Get10msAudioFromFile(
109     int16_t* outBuffer,
110     int& lengthInSamples,
111     int frequencyInHz)
112 {
113     if(_codec.plfreq == 0)
114     {
115         LOG(LS_WARNING) << "Get10msAudioFromFile() playing not started!"
116                         << " codec freq = " << _codec.plfreq
117                         << ", wanted freq = " << frequencyInHz;
118         return -1;
119     }
120 
121     AudioFrame unresampledAudioFrame;
122     if(STR_CASE_CMP(_codec.plname, "L16") == 0)
123     {
124         unresampledAudioFrame.sample_rate_hz_ = _codec.plfreq;
125 
126         // L16 is un-encoded data. Just pull 10 ms.
127         uint32_t lengthInBytes =
128             sizeof(unresampledAudioFrame.data_);
129         if (_fileModule.PlayoutAudioData(
130                 (int8_t*)unresampledAudioFrame.data_,
131                 lengthInBytes) == -1)
132         {
133             // End of file reached.
134             return -1;
135         }
136         if(lengthInBytes == 0)
137         {
138             lengthInSamples = 0;
139             return 0;
140         }
141         // One sample is two bytes.
142         unresampledAudioFrame.samples_per_channel_ =
143             (uint16_t)lengthInBytes >> 1;
144 
145     }else {
146         // Decode will generate 10 ms of audio data. PlayoutAudioData(..)
147         // expects a full frame. If the frame size is larger than 10 ms,
148         // PlayoutAudioData(..) data should be called proportionally less often.
149         int16_t encodedBuffer[MAX_AUDIO_BUFFER_IN_SAMPLES];
150         uint32_t encodedLengthInBytes = 0;
151         if(++_numberOf10MsInDecoder >= _numberOf10MsPerFrame)
152         {
153             _numberOf10MsInDecoder = 0;
154             uint32_t bytesFromFile = sizeof(encodedBuffer);
155             if (_fileModule.PlayoutAudioData((int8_t*)encodedBuffer,
156                                              bytesFromFile) == -1)
157             {
158                 // End of file reached.
159                 return -1;
160             }
161             encodedLengthInBytes = bytesFromFile;
162         }
163         if(_audioDecoder.Decode(unresampledAudioFrame,frequencyInHz,
164                                 (int8_t*)encodedBuffer,
165                                 encodedLengthInBytes) == -1)
166         {
167             return -1;
168         }
169     }
170 
171     int outLen = 0;
172     if(_resampler.ResetIfNeeded(unresampledAudioFrame.sample_rate_hz_,
173                                 frequencyInHz, kResamplerSynchronous))
174     {
175         LOG(LS_WARNING) << "Get10msAudioFromFile() unexpected codec.";
176 
177         // New sampling frequency. Update state.
178         outLen = frequencyInHz / 100;
179         memset(outBuffer, 0, outLen * sizeof(int16_t));
180         return 0;
181     }
182     _resampler.Push(unresampledAudioFrame.data_,
183                     unresampledAudioFrame.samples_per_channel_,
184                     outBuffer,
185                     MAX_AUDIO_BUFFER_IN_SAMPLES,
186                     outLen);
187 
188     lengthInSamples = outLen;
189 
190     if(_scaling != 1.0)
191     {
192         for (int i = 0;i < outLen; i++)
193         {
194             outBuffer[i] = (int16_t)(outBuffer[i] * _scaling);
195         }
196     }
197     _decodedLengthInMS += 10;
198     return 0;
199 }
200 
RegisterModuleFileCallback(FileCallback * callback)201 int32_t FilePlayerImpl::RegisterModuleFileCallback(FileCallback* callback)
202 {
203     return _fileModule.SetModuleFileCallback(callback);
204 }
205 
SetAudioScaling(float scaleFactor)206 int32_t FilePlayerImpl::SetAudioScaling(float scaleFactor)
207 {
208     if((scaleFactor >= 0)&&(scaleFactor <= 2.0))
209     {
210         _scaling = scaleFactor;
211         return 0;
212     }
213     LOG(LS_WARNING) << "SetAudioScaling() non-allowed scale factor.";
214     return -1;
215 }
216 
StartPlayingFile(const char * fileName,bool loop,uint32_t startPosition,float volumeScaling,uint32_t notification,uint32_t stopPosition,const CodecInst * codecInst)217 int32_t FilePlayerImpl::StartPlayingFile(const char* fileName,
218                                          bool loop,
219                                          uint32_t startPosition,
220                                          float volumeScaling,
221                                          uint32_t notification,
222                                          uint32_t stopPosition,
223                                          const CodecInst* codecInst)
224 {
225     if (_fileFormat == kFileFormatPcm16kHzFile ||
226         _fileFormat == kFileFormatPcm8kHzFile||
227         _fileFormat == kFileFormatPcm32kHzFile )
228     {
229         CodecInst codecInstL16;
230         strncpy(codecInstL16.plname,"L16",32);
231         codecInstL16.pltype   = 93;
232         codecInstL16.channels = 1;
233 
234         if (_fileFormat == kFileFormatPcm8kHzFile)
235         {
236             codecInstL16.rate     = 128000;
237             codecInstL16.plfreq   = 8000;
238             codecInstL16.pacsize  = 80;
239 
240         } else if(_fileFormat == kFileFormatPcm16kHzFile)
241         {
242             codecInstL16.rate     = 256000;
243             codecInstL16.plfreq   = 16000;
244             codecInstL16.pacsize  = 160;
245 
246         }else if(_fileFormat == kFileFormatPcm32kHzFile)
247         {
248             codecInstL16.rate     = 512000;
249             codecInstL16.plfreq   = 32000;
250             codecInstL16.pacsize  = 160;
251         } else
252         {
253             LOG(LS_ERROR) << "StartPlayingFile() sample frequency not "
254                           << "supported for PCM format.";
255             return -1;
256         }
257 
258         if (_fileModule.StartPlayingAudioFile(fileName, notification, loop,
259                                               _fileFormat, &codecInstL16,
260                                               startPosition,
261                                               stopPosition) == -1)
262         {
263             LOG(LS_WARNING) << "StartPlayingFile() failed to initialize "
264                             << "pcm file " << fileName;
265             return -1;
266         }
267         SetAudioScaling(volumeScaling);
268     }else if(_fileFormat == kFileFormatPreencodedFile)
269     {
270         if (_fileModule.StartPlayingAudioFile(fileName, notification, loop,
271                                               _fileFormat, codecInst) == -1)
272         {
273             LOG(LS_WARNING) << "StartPlayingFile() failed to initialize "
274                             << "pre-encoded file " << fileName;
275             return -1;
276         }
277     } else
278     {
279         CodecInst* no_inst = NULL;
280         if (_fileModule.StartPlayingAudioFile(fileName, notification, loop,
281                                               _fileFormat, no_inst,
282                                               startPosition,
283                                               stopPosition) == -1)
284         {
285             LOG(LS_WARNING) << "StartPlayingFile() failed to initialize file "
286                             << fileName;
287             return -1;
288         }
289         SetAudioScaling(volumeScaling);
290     }
291     if (SetUpAudioDecoder() == -1)
292     {
293         StopPlayingFile();
294         return -1;
295     }
296     return 0;
297 }
298 
StartPlayingFile(InStream & sourceStream,uint32_t startPosition,float volumeScaling,uint32_t notification,uint32_t stopPosition,const CodecInst * codecInst)299 int32_t FilePlayerImpl::StartPlayingFile(InStream& sourceStream,
300                                          uint32_t startPosition,
301                                          float volumeScaling,
302                                          uint32_t notification,
303                                          uint32_t stopPosition,
304                                          const CodecInst* codecInst)
305 {
306     if (_fileFormat == kFileFormatPcm16kHzFile ||
307         _fileFormat == kFileFormatPcm32kHzFile ||
308         _fileFormat == kFileFormatPcm8kHzFile)
309     {
310         CodecInst codecInstL16;
311         strncpy(codecInstL16.plname,"L16",32);
312         codecInstL16.pltype   = 93;
313         codecInstL16.channels = 1;
314 
315         if (_fileFormat == kFileFormatPcm8kHzFile)
316         {
317             codecInstL16.rate     = 128000;
318             codecInstL16.plfreq   = 8000;
319             codecInstL16.pacsize  = 80;
320 
321         }else if (_fileFormat == kFileFormatPcm16kHzFile)
322         {
323             codecInstL16.rate     = 256000;
324             codecInstL16.plfreq   = 16000;
325             codecInstL16.pacsize  = 160;
326 
327         }else if (_fileFormat == kFileFormatPcm32kHzFile)
328         {
329             codecInstL16.rate     = 512000;
330             codecInstL16.plfreq   = 32000;
331             codecInstL16.pacsize  = 160;
332         }else
333         {
334             LOG(LS_ERROR) << "StartPlayingFile() sample frequency not "
335                           << "supported for PCM format.";
336             return -1;
337         }
338         if (_fileModule.StartPlayingAudioStream(sourceStream, notification,
339                                                 _fileFormat, &codecInstL16,
340                                                 startPosition,
341                                                 stopPosition) == -1)
342         {
343             LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream "
344                           << "playout.";
345             return -1;
346         }
347 
348     }else if(_fileFormat == kFileFormatPreencodedFile)
349     {
350         if (_fileModule.StartPlayingAudioStream(sourceStream, notification,
351                                                 _fileFormat, codecInst) == -1)
352         {
353             LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream "
354                           << "playout.";
355             return -1;
356         }
357     } else {
358         CodecInst* no_inst = NULL;
359         if (_fileModule.StartPlayingAudioStream(sourceStream, notification,
360                                                 _fileFormat, no_inst,
361                                                 startPosition,
362                                                 stopPosition) == -1)
363         {
364             LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream "
365                           << "playout.";
366             return -1;
367         }
368     }
369     SetAudioScaling(volumeScaling);
370 
371     if (SetUpAudioDecoder() == -1)
372     {
373         StopPlayingFile();
374         return -1;
375     }
376     return 0;
377 }
378 
StopPlayingFile()379 int32_t FilePlayerImpl::StopPlayingFile()
380 {
381     memset(&_codec, 0, sizeof(CodecInst));
382     _numberOf10MsPerFrame  = 0;
383     _numberOf10MsInDecoder = 0;
384     return _fileModule.StopPlaying();
385 }
386 
IsPlayingFile() const387 bool FilePlayerImpl::IsPlayingFile() const
388 {
389     return _fileModule.IsPlaying();
390 }
391 
GetPlayoutPosition(uint32_t & durationMs)392 int32_t FilePlayerImpl::GetPlayoutPosition(uint32_t& durationMs)
393 {
394     return _fileModule.PlayoutPositionMs(durationMs);
395 }
396 
SetUpAudioDecoder()397 int32_t FilePlayerImpl::SetUpAudioDecoder()
398 {
399     if ((_fileModule.codec_info(_codec) == -1))
400     {
401         LOG(LS_WARNING) << "Failed to retrieve codec info of file data.";
402         return -1;
403     }
404     if( STR_CASE_CMP(_codec.plname, "L16") != 0 &&
405         _audioDecoder.SetDecodeCodec(_codec,AMRFileStorage) == -1)
406     {
407         LOG(LS_WARNING) << "SetUpAudioDecoder() codec " << _codec.plname
408                         << " not supported.";
409         return -1;
410     }
411     _numberOf10MsPerFrame = _codec.pacsize / (_codec.plfreq / 100);
412     _numberOf10MsInDecoder = 0;
413     return 0;
414 }
415 
416 #ifdef WEBRTC_MODULE_UTILITY_VIDEO
VideoFilePlayerImpl(uint32_t instanceID,FileFormats fileFormat)417 VideoFilePlayerImpl::VideoFilePlayerImpl(uint32_t instanceID,
418                                          FileFormats fileFormat)
419     : FilePlayerImpl(instanceID, fileFormat),
420       video_decoder_(new VideoCoder()),
421       video_codec_info_(),
422       _decodedVideoFrames(0),
423       _encodedData(*new EncodedVideoData()),
424       _frameScaler(*new FrameScaler()),
425       _critSec(CriticalSectionWrapper::CreateCriticalSection()),
426       _startTime(),
427       _accumulatedRenderTimeMs(0),
428       _frameLengthMS(0),
429       _numberOfFramesRead(0),
430       _videoOnly(false) {
431   memset(&video_codec_info_, 0, sizeof(video_codec_info_));
432 }
433 
~VideoFilePlayerImpl()434 VideoFilePlayerImpl::~VideoFilePlayerImpl()
435 {
436     delete _critSec;
437     delete &_frameScaler;
438     video_decoder_.reset();
439     delete &_encodedData;
440 }
441 
StartPlayingVideoFile(const char * fileName,bool loop,bool videoOnly)442 int32_t VideoFilePlayerImpl::StartPlayingVideoFile(
443     const char* fileName,
444     bool loop,
445     bool videoOnly)
446 {
447     CriticalSectionScoped lock( _critSec);
448 
449     if(_fileModule.StartPlayingVideoFile(fileName, loop, videoOnly,
450                                          _fileFormat) != 0)
451     {
452         return -1;
453     }
454 
455     _decodedVideoFrames = 0;
456     _accumulatedRenderTimeMs = 0;
457     _frameLengthMS = 0;
458     _numberOfFramesRead = 0;
459     _videoOnly = videoOnly;
460 
461     // Set up video_codec_info_ according to file,
462     if(SetUpVideoDecoder() != 0)
463     {
464         StopPlayingFile();
465         return -1;
466     }
467     if(!videoOnly)
468     {
469         // Set up _codec according to file,
470         if(SetUpAudioDecoder() != 0)
471         {
472             StopPlayingFile();
473             return -1;
474         }
475     }
476     return 0;
477 }
478 
StopPlayingFile()479 int32_t VideoFilePlayerImpl::StopPlayingFile()
480 {
481     CriticalSectionScoped lock( _critSec);
482 
483     _decodedVideoFrames = 0;
484     video_decoder_.reset(new VideoCoder());
485 
486     return FilePlayerImpl::StopPlayingFile();
487 }
488 
GetVideoFromFile(I420VideoFrame & videoFrame,uint32_t outWidth,uint32_t outHeight)489 int32_t VideoFilePlayerImpl::GetVideoFromFile(I420VideoFrame& videoFrame,
490                                               uint32_t outWidth,
491                                               uint32_t outHeight)
492 {
493     CriticalSectionScoped lock( _critSec);
494 
495     int32_t retVal = GetVideoFromFile(videoFrame);
496     if(retVal != 0)
497     {
498         return retVal;
499     }
500     if (!videoFrame.IsZeroSize())
501     {
502         retVal = _frameScaler.ResizeFrameIfNeeded(&videoFrame, outWidth,
503                                                   outHeight);
504     }
505     return retVal;
506 }
507 
GetVideoFromFile(I420VideoFrame & videoFrame)508 int32_t VideoFilePlayerImpl::GetVideoFromFile(I420VideoFrame& videoFrame)
509 {
510     CriticalSectionScoped lock( _critSec);
511     // No new video data read from file.
512     if(_encodedData.payloadSize == 0)
513     {
514         videoFrame.ResetSize();
515         return -1;
516     }
517     int32_t retVal = 0;
518     if(strncmp(video_codec_info_.plName, "I420", 5) == 0)
519     {
520       int size_y = video_codec_info_.width * video_codec_info_.height;
521       int half_width = (video_codec_info_.width + 1) / 2;
522       int half_height = (video_codec_info_.height + 1) / 2;
523       int size_uv = half_width * half_height;
524 
525       // TODO(mikhal): Do we need to align the stride here?
526       const uint8_t* buffer_y = _encodedData.payloadData;
527       const uint8_t* buffer_u = buffer_y + size_y;
528       const uint8_t* buffer_v = buffer_u + size_uv;
529       videoFrame.CreateFrame(size_y, buffer_y,
530                              size_uv, buffer_u,
531                              size_uv, buffer_v,
532                              video_codec_info_.width, video_codec_info_.height,
533                              video_codec_info_.height, half_width, half_width);
534     }else
535     {
536         // Set the timestamp manually since there is no timestamp in the file.
537         // Update timestam according to 90 kHz stream.
538         _encodedData.timeStamp += (90000 / video_codec_info_.maxFramerate);
539         retVal = video_decoder_->Decode(videoFrame, _encodedData);
540     }
541 
542     int64_t renderTimeMs = TickTime::MillisecondTimestamp();
543     videoFrame.set_render_time_ms(renderTimeMs);
544 
545      // Indicate that the current frame in the encoded buffer is old/has
546      // already been read.
547     _encodedData.payloadSize = 0;
548     if( retVal == 0)
549     {
550         _decodedVideoFrames++;
551     }
552     return retVal;
553 }
554 
video_codec_info(VideoCodec & videoCodec) const555 int32_t VideoFilePlayerImpl::video_codec_info(
556     VideoCodec& videoCodec) const
557 {
558     if(video_codec_info_.plName[0] == 0)
559     {
560         return -1;
561     }
562     memcpy(&videoCodec, &video_codec_info_, sizeof(VideoCodec));
563     return 0;
564 }
565 
TimeUntilNextVideoFrame()566 int32_t VideoFilePlayerImpl::TimeUntilNextVideoFrame()
567 {
568     if(_fileFormat != kFileFormatAviFile)
569     {
570         return -1;
571     }
572     if(!_fileModule.IsPlaying())
573     {
574         return -1;
575     }
576     if(_encodedData.payloadSize <= 0)
577     {
578         // Read next frame from file.
579         CriticalSectionScoped lock( _critSec);
580 
581         if(_fileFormat == kFileFormatAviFile)
582         {
583             // Get next video frame
584             uint32_t encodedBufferLengthInBytes = _encodedData.bufferSize;
585             if(_fileModule.PlayoutAVIVideoData(
586                    reinterpret_cast< int8_t*>(_encodedData.payloadData),
587                    encodedBufferLengthInBytes) != 0)
588             {
589                 LOG(LS_WARNING) << "Error reading video data.";
590                 return -1;
591             }
592             _encodedData.payloadSize = encodedBufferLengthInBytes;
593             _encodedData.codec = video_codec_info_.codecType;
594             _numberOfFramesRead++;
595 
596             if(_accumulatedRenderTimeMs == 0)
597             {
598                 _startTime = TickTime::Now();
599                 // This if-statement should only trigger once.
600                 _accumulatedRenderTimeMs = 1;
601             } else {
602                 // A full seconds worth of frames have been read.
603                 if(_numberOfFramesRead % video_codec_info_.maxFramerate == 0)
604                 {
605                     // Frame rate is in frames per seconds. Frame length is
606                     // calculated as an integer division which means it may
607                     // be rounded down. Compensate for this every second.
608                     uint32_t rest = 1000%_frameLengthMS;
609                     _accumulatedRenderTimeMs += rest;
610                 }
611                 _accumulatedRenderTimeMs += _frameLengthMS;
612             }
613         }
614     }
615 
616     int64_t timeToNextFrame;
617     if(_videoOnly)
618     {
619         timeToNextFrame = _accumulatedRenderTimeMs -
620             (TickTime::Now() - _startTime).Milliseconds();
621 
622     } else {
623         // Synchronize with the audio stream instead of system clock.
624         timeToNextFrame = _accumulatedRenderTimeMs - _decodedLengthInMS;
625     }
626     if(timeToNextFrame < 0)
627     {
628         return 0;
629 
630     } else if(timeToNextFrame > 0x0fffffff)
631     {
632         // Wraparound or audio stream has gone to far ahead of the video stream.
633         return -1;
634     }
635     return static_cast<int32_t>(timeToNextFrame);
636 }
637 
SetUpVideoDecoder()638 int32_t VideoFilePlayerImpl::SetUpVideoDecoder()
639 {
640     if (_fileModule.VideoCodecInst(video_codec_info_) != 0)
641     {
642         LOG(LS_WARNING) << "SetVideoDecoder() failed to retrieve codec info of "
643                         << "file data.";
644         return -1;
645     }
646 
647     int32_t useNumberOfCores = 1;
648     if (video_decoder_->SetDecodeCodec(video_codec_info_, useNumberOfCores) !=
649         0) {
650         LOG(LS_WARNING) << "SetUpVideoDecoder() codec "
651                         << video_codec_info_.plName << " not supported.";
652         return -1;
653     }
654 
655     _frameLengthMS = 1000/video_codec_info_.maxFramerate;
656 
657     // Size of unencoded data (I420) should be the largest possible frame size
658     // in a file.
659     const uint32_t KReadBufferSize = 3 * video_codec_info_.width *
660         video_codec_info_.height / 2;
661     _encodedData.VerifyAndAllocate(KReadBufferSize);
662     _encodedData.encodedHeight = video_codec_info_.height;
663     _encodedData.encodedWidth = video_codec_info_.width;
664     _encodedData.payloadType = video_codec_info_.plType;
665     _encodedData.timeStamp = 0;
666     return 0;
667 }
668 #endif // WEBRTC_MODULE_UTILITY_VIDEO
669 }  // namespace webrtc
670