1 /*
2  * recording.cxx
3  *
4  * OPAL call recording
5  *
6  * Open Phone Abstraction Library (OPAL)
7  *
8  * Copyright (C) 2009 Post Increment
9  *
10  * The contents of this file are subject to the Mozilla Public License
11  * Version 1.0 (the "License"); you may not use this file except in
12  * compliance with the License. You may obtain a copy of the License at
13  * http://www.mozilla.org/MPL/
14  *
15  * Software distributed under the License is distributed on an "AS IS"
16  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17  * the License for the specific language governing rights and limitations
18  * under the License.
19  *
20  * The Original Code is Open Phone Abstraction Library.
21  *
22  * The Initial Developer of the Original Code is Post Increment
23  *
24  * Contributor(s): ______________________________________.
25  *
26  * $Revision: 27141 $
27  * $Author: rjongbloed $
28  * $Date: 2012-03-06 21:56:21 -0600 (Tue, 06 Mar 2012) $
29  */
30 
31 
32 #include <ptlib.h>
33 
34 #include <opal/buildopts.h>
35 
36 #if OPAL_HAS_MIXER
37 
38 #include <opal/opalmixer.h>
39 #include <opal/recording.h>
40 #include <codec/opalwavfile.h>
41 
42 
43 //////////////////////////////////////////////////////////////////////////////
44 
45 /** This class manages the recording of OPAL calls to WAV files.
46   */
47 class OpalWAVRecordManager : public OpalRecordManager
48 {
49   public:
50     OpalWAVRecordManager();
51     ~OpalWAVRecordManager();
52 
53     virtual bool OpenFile(const PFilePath & fn);
54     virtual bool IsOpen() const;
55     virtual bool Close();
56     virtual bool OpenStream(const PString & strmId, const OpalMediaFormat & format);
57     virtual bool CloseStream(const PString & strmId);
58     virtual bool WriteAudio(const PString & strmId, const RTP_DataFrame & rtp);
59     virtual bool WriteVideo(const PString & strmId, const RTP_DataFrame & rtp);
60 
61   protected:
62     struct Mixer : public OpalAudioMixer {
MixerOpalWAVRecordManager::Mixer63       Mixer() { }
~MixerOpalWAVRecordManager::Mixer64       ~Mixer() { StopPushThread(); }
65 
66       bool Open(const PFilePath & fn, const Options & options);
67       virtual bool OnMixed(RTP_DataFrame * & output);
68 
69       OpalWAVFile m_file;
70     } * m_mixer;
71 
72     PMutex m_mutex;
73 };
74 
75 PFACTORY_CREATE(OpalRecordManager::Factory, OpalWAVRecordManager, ".wav", false);
76 
77 
OpalWAVRecordManager()78 OpalWAVRecordManager::OpalWAVRecordManager()
79   :	m_mixer(NULL)
80 {
81 }
82 
83 
~OpalWAVRecordManager()84 OpalWAVRecordManager::~OpalWAVRecordManager()
85 {
86   Close();
87 }
88 
89 
OpenFile(const PFilePath & fn)90 bool OpalWAVRecordManager::OpenFile(const PFilePath & fn)
91 {
92   if (m_options.m_audioFormat.IsEmpty())
93     m_options.m_audioFormat = OpalPCM16.GetName();
94 
95   PWaitAndSignal mutex(m_mutex);
96 
97   if (IsOpen()) {
98     PTRACE(2, "OpalRecord\tCannot open mixer after it has started.");
99     return false;
100   }
101 
102   m_mixer = new Mixer();
103   if (m_mixer->Open(fn, m_options))
104     return true;
105 
106   delete m_mixer;
107   m_mixer = NULL;
108   return false;
109 }
110 
111 
IsOpen() const112 bool OpalWAVRecordManager::IsOpen() const
113 {
114   PWaitAndSignal mutex(m_mutex);
115   return m_mixer != NULL && m_mixer->m_file.IsOpen();
116 }
117 
118 
Close()119 bool OpalWAVRecordManager::Close()
120 {
121   m_mutex.Wait();
122 
123   delete m_mixer;
124   m_mixer = NULL;
125 
126   m_mutex.Signal();
127 
128   return true;
129 }
130 
131 
OpenStream(const PString & strmId,const OpalMediaFormat & format)132 bool OpalWAVRecordManager::OpenStream(const PString & strmId, const OpalMediaFormat & format)
133 {
134   PWaitAndSignal mutex(m_mutex);
135 
136   if (m_mixer == NULL || format.GetMediaType() != OpalMediaType::Audio())
137     return false;
138 
139   m_mixer->m_file.SetSampleRate(format.GetClockRate());
140   return m_mixer->SetSampleRate(format.GetClockRate()) &&
141          m_mixer->AddStream(strmId);
142 }
143 
144 
CloseStream(const PString & streamId)145 bool OpalWAVRecordManager::CloseStream(const PString & streamId)
146 {
147   m_mutex.Wait();
148   if (m_mixer != NULL)
149     m_mixer->RemoveStream(streamId);
150   m_mutex.Signal();
151 
152   PTRACE(4, "OpalRecord\tClosed stream " << streamId);
153   return true;
154 }
155 
156 
WriteAudio(const PString & strm,const RTP_DataFrame & rtp)157 bool OpalWAVRecordManager::WriteAudio(const PString & strm, const RTP_DataFrame & rtp)
158 {
159   PWaitAndSignal mutex(m_mutex);
160   return m_mixer != NULL && m_mixer->WriteStream(strm, rtp);
161 }
162 
163 
WriteVideo(const PString &,const RTP_DataFrame &)164 bool OpalWAVRecordManager::WriteVideo(const PString &, const RTP_DataFrame &)
165 {
166   return false;
167 }
168 
169 
Open(const PFilePath & fn,const Options & options)170 bool OpalWAVRecordManager::Mixer::Open(const PFilePath & fn, const Options & options)
171 {
172   if (!m_file.SetFormat(options.m_audioFormat)) {
173     PTRACE(2, "OpalRecord\tWAV file recording does not support format " << options.m_audioFormat);
174     return false;
175   }
176 
177   if (!m_file.Open(fn, PFile::ReadWrite, PFile::Create|PFile::Truncate)) {
178     PTRACE(2, "OpalRecord\tCould not open file \"" << fn << '"');
179     return false;
180   }
181 
182   if (options.m_stereo) {
183     m_file.SetChannels(2);
184     if (m_file.GetChannels() == 2)
185       m_stereo = true;
186   }
187 
188   PTRACE(4, "OpalRecord\t" << (m_stereo ? "Stereo" : "Mono") << " mixer opened for file \"" << fn << '"');
189   return true;
190 }
191 
192 
OnMixed(RTP_DataFrame * & output)193 bool OpalWAVRecordManager::Mixer::OnMixed(RTP_DataFrame * & output)
194 {
195   if (!m_file.IsOpen())
196     return false;
197 
198   if (m_file.Write(output->GetPayloadPtr(), output->GetPayloadSize()))
199     return true;
200 
201   PTRACE(1, "OpalRecord\tError writing WAV file " << m_file.GetFilePath());
202   return false;
203 }
204 
205 
206 /////////////////////////////////////////////////////////////////////////////
207 
208 #if OPAL_VIDEO && P_VFW_CAPTURE
209 
210 #include <ptlib/vconvert.h>
211 
212 #include <vfw.h>
213 #pragma comment(lib, "vfw32.lib")
214 
215 
216 /** This class manages the recording of OPAL calls to AVI files.
217   */
218 class OpalAVIRecordManager : public OpalRecordManager
219 {
220   protected:
221     struct AudioMixer : public OpalAudioMixer
222     {
AudioMixerOpalAVIRecordManager::AudioMixer223       AudioMixer(OpalAVIRecordManager & manager, bool stereo)
224         : OpalAudioMixer(stereo), m_manager(manager) { }
~AudioMixerOpalAVIRecordManager::AudioMixer225       ~AudioMixer() { StopPushThread(); }
OnMixedOpalAVIRecordManager::AudioMixer226       virtual bool OnMixed(RTP_DataFrame * & output) { return m_manager.OnMixedAudio(*output); }
227       OpalAVIRecordManager & m_manager;
228     } * m_audioMixer;
229 
230     struct VideoMixer : public OpalVideoMixer
231     {
VideoMixerOpalAVIRecordManager::VideoMixer232       VideoMixer(OpalAVIRecordManager & manager, OpalVideoMixer::Styles style, unsigned width, unsigned height)
233         : OpalVideoMixer(style, width, height), m_manager(manager) { }
~VideoMixerOpalAVIRecordManager::VideoMixer234       ~VideoMixer() { StopPushThread(); }
OnMixedOpalAVIRecordManager::VideoMixer235       virtual bool OnMixed(RTP_DataFrame * & output) { return m_manager.OnMixedVideo(*output); }
236       OpalAVIRecordManager & m_manager;
237     } * m_videoMixer;
238 
239   public:
240     OpalAVIRecordManager();
241     ~OpalAVIRecordManager();
242 
243     virtual bool OpenFile(const PFilePath & fn);
244     virtual bool IsOpen() const;
245     virtual bool Close();
246     virtual bool OpenStream(const PString & strmId, const OpalMediaFormat & format);
247     virtual bool CloseStream(const PString & strmId);
248     virtual bool WriteAudio(const PString & strmId, const RTP_DataFrame & rtp);
249     virtual bool WriteVideo(const PString & strmId, const RTP_DataFrame & rtp);
250 
251   protected:
252     // Callback from OpalAudioMixer
253     virtual bool OpenAudio(const PString & strmId, const OpalMediaFormat & format);
254     virtual bool OnMixedAudio(const RTP_DataFrame & frame);
255 
256     // Callback from OpalVideoMixer
257     virtual bool OpenVideo(const PString & strmId, const OpalMediaFormat & format);
258     virtual bool OnMixedVideo(const RTP_DataFrame & frame);
259 
260     #if PTRACING
IsResultError(HRESULT result,const char * msg)261       bool IsResultError(HRESULT result, const char * msg)
262       {
263         if (result == AVIERR_OK)
264           return false;
265 
266         if (!PTrace::CanTrace(2))
267           return true;
268 
269         ostream & strm = PTrace::Begin(2, __FILE__, __LINE__);
270         strm << "OpalRecord\tError " << msg << ": ";
271         switch (result) {
272           case AVIERR_UNSUPPORTED :
273             strm << "Unsupported compressor '" << m_options.m_videoFormat << '\'';
274             break;
275 
276           case AVIERR_NOCOMPRESSOR :
277             strm << "No compressor '" << m_options.m_videoFormat << '\'';
278             break;
279 
280           default :
281             strm << "Error=0x" << hex << result;
282         }
283         strm << PTrace::End;
284 
285         return true;
286       }
287 
288       #define IS_RESULT_ERROR(result, msg) IsResultError(result, msg)
289 
290     #else
291 
292       #define IS_RESULT_ERROR(result, msg) ((result) != AVIERR_OK)
293 
294     #endif
295 
296 
297     PMutex     m_mutex;
298     PAVIFILE   m_file;
299     PAVISTREAM m_audioStream;
300     DWORD      m_audioSampleCount;
301     DWORD      m_audioSampleSize;
302     PAVISTREAM m_videoStream;
303     PAVISTREAM m_videoCompressor;
304     DWORD      m_VideoFrameCount;
305     PBYTEArray m_videoBuffer;
306     PColourConverter * m_videoConverter;
307 };
308 
309 PFACTORY_CREATE(OpalRecordManager::Factory, OpalAVIRecordManager, ".avi", false);
310 
311 
312 static char const * const VideoModeNames[OpalRecordManager::NumVideoMixingModes] = {
313   "Side by Side Letterbox Video",
314   "Side by Side Scaled Video",
315   "Stacked Pillarbox Video",
316   "Stacked Scaled Video",
317   "Separate Video Stream"
318 };
319 
320 
OpalAVIRecordManager()321 OpalAVIRecordManager::OpalAVIRecordManager()
322   :	m_audioMixer(NULL)
323   ,	m_videoMixer(NULL)
324   ,	m_file(NULL)
325   , m_audioStream(NULL)
326   , m_audioSampleCount(0)
327   , m_audioSampleSize(sizeof(short))
328   , m_videoStream(NULL)
329   , m_videoCompressor(NULL)
330   , m_VideoFrameCount(0)
331   , m_videoConverter(NULL)
332 {
333   AVIFileInit();
334 }
335 
336 
~OpalAVIRecordManager()337 OpalAVIRecordManager::~OpalAVIRecordManager()
338 {
339   Close();
340   AVIFileExit();
341 }
342 
343 
OpenFile(const PFilePath & fn)344 bool OpalAVIRecordManager::OpenFile(const PFilePath & fn)
345 {
346   if (m_options.m_audioFormat.IsEmpty())
347     m_options.m_audioFormat = OpalPCM16.GetName();
348   else if (m_options.m_audioFormat != OpalPCM16) {
349     PTRACE(2, "OpalRecord\tAVI file recording does not (yet) support format " << m_options.m_audioFormat);
350     return false;
351   }
352 
353   if (m_options.m_videoFormat.IsEmpty())
354     m_options.m_videoFormat = "MSVC"; // Default to Microsoft Video 1, every system has that!
355   else if (m_options.m_videoFormat.GetLength() != 4) {
356     PTRACE(2, "OpalRecord\tAVI file recording does not (yet) support format " << m_options.m_videoFormat);
357     return false;
358   }
359 
360   PWaitAndSignal mutex(m_mutex);
361 
362   if (m_file != NULL) {
363     PTRACE(2, "OpalRecord\tCannot open mixer after it has started.");
364     return false;
365   }
366 
367   if (IS_RESULT_ERROR(AVIFileOpen(&m_file, fn, OF_WRITE|OF_CREATE, NULL), "creating AVI file"))
368     return false;
369 
370   m_audioMixer = new AudioMixer(*this, m_options.m_stereo);
371 
372   OpalVideoMixer::Styles style;
373   switch (m_options.m_videoMixing) {
374     case eSideBySideScaled :
375       m_options.m_videoWidth *= 2;
376       style = OpalVideoMixer::eSideBySideScaled;
377       break;
378 
379     case eSideBySideLetterbox :
380       style = OpalVideoMixer::eSideBySideLetterbox;
381       break;
382 
383     case eStackedScaled :
384       m_options.m_videoHeight *= 2;
385       style = OpalVideoMixer::eStackedScaled;
386       break;
387 
388     case eStackedPillarbox :
389       style = OpalVideoMixer::eStackedPillarbox;
390       break;
391 
392     default :
393       PAssertAlways(PInvalidParameter);
394       return false;
395   }
396 
397   m_videoMixer = new VideoMixer(*this, style, m_options.m_videoWidth, m_options.m_videoHeight);
398 
399   PTRACE(4, "OpalRecord\t" << (m_options.m_stereo ? "Stereo" : "Mono") << "-PCM/"
400          << m_options.m_videoFormat << "-Video mixers opened for file \"" << fn << '"');
401   return true;
402 }
403 
404 
IsOpen() const405 bool OpalAVIRecordManager::IsOpen() const
406 {
407   return m_file != NULL;
408 }
409 
410 
Close()411 bool OpalAVIRecordManager::Close()
412 {
413   m_mutex.Wait();
414 
415   delete m_audioMixer;
416   m_audioMixer = NULL;
417 
418   delete m_videoMixer;
419   m_videoMixer = NULL;
420 
421   delete m_videoConverter;
422   m_videoConverter = NULL;
423 
424   if (m_videoCompressor != NULL) {
425     AVIStreamRelease(m_videoCompressor);
426     m_videoCompressor = NULL;
427   }
428 
429   if (m_videoStream != NULL) {
430     AVIStreamRelease(m_videoStream);
431     m_videoStream = NULL;
432   }
433 
434   if (m_audioStream != NULL) {
435     AVIStreamRelease(m_audioStream);
436     m_audioStream = NULL;
437   }
438 
439   if (m_file != NULL) {
440     AVIFileRelease(m_file);
441     m_file = NULL;
442   }
443 
444   m_mutex.Signal();
445 
446   return true;
447 }
448 
449 
OpenStream(const PString & strmId,const OpalMediaFormat & format)450 bool OpalAVIRecordManager::OpenStream(const PString & strmId, const OpalMediaFormat & format)
451 {
452   PWaitAndSignal mutex(m_mutex);
453 
454   if (format.GetMediaType() == OpalMediaType::Audio())
455     return OpenAudio(strmId, format);
456 
457   if (format.GetMediaType() == OpalMediaType::Video())
458     return OpenVideo(strmId, format);
459 
460   return false;
461 }
462 
463 
CloseStream(const PString & streamId)464 bool OpalAVIRecordManager::CloseStream(const PString & streamId)
465 {
466   m_mutex.Wait();
467 
468   if (m_audioMixer != NULL)
469     m_audioMixer->RemoveStream(streamId);
470 
471   if (m_videoMixer != NULL)
472     m_videoMixer->RemoveStream(streamId);
473 
474   m_mutex.Signal();
475 
476   PTRACE(4, "OpalRecord\tClosed stream " << streamId);
477   return true;
478 }
479 
480 
WriteAudio(const PString & strmId,const RTP_DataFrame & rtp)481 bool OpalAVIRecordManager::WriteAudio(const PString & strmId, const RTP_DataFrame & rtp)
482 {
483   PWaitAndSignal mutex(m_mutex);
484   return m_audioMixer != NULL && m_audioMixer->WriteStream(strmId, rtp);
485 }
486 
487 
WriteVideo(const PString & strmId,const RTP_DataFrame & rtp)488 bool OpalAVIRecordManager::WriteVideo(const PString & strmId, const RTP_DataFrame & rtp)
489 {
490   PWaitAndSignal mutex(m_mutex);
491   return m_videoMixer != NULL && m_videoMixer->WriteStream(strmId, rtp);
492 }
493 
494 
OpenAudio(const PString & strmId,const OpalMediaFormat & format)495 bool OpalAVIRecordManager::OpenAudio(const PString & strmId, const OpalMediaFormat & format)
496 {
497   if (m_audioMixer == NULL)
498     return false;
499 
500   if (!m_audioMixer->SetSampleRate(format.GetClockRate()))
501     return false;
502 
503   if (m_audioStream != NULL)
504     return m_audioMixer->AddStream(strmId);
505 
506   PTRACE(4, "OpalRecord\tCreating AVI stream for audio format '" << m_options.m_audioFormat << '\'');
507 
508   WAVEFORMATEX fmt;
509   fmt.wFormatTag = WAVE_FORMAT_PCM;
510   fmt.wBitsPerSample = 16;
511   fmt.nChannels = m_audioMixer->IsStereo() ? 2 : 1;
512   fmt.nSamplesPerSec = m_audioMixer->GetSampleRate();
513   fmt.nBlockAlign = (fmt.nChannels*fmt.wBitsPerSample+7)/8;
514   fmt.nAvgBytesPerSec = fmt.nSamplesPerSec*fmt.nBlockAlign;
515   fmt.cbSize = 0;
516 
517   m_audioSampleSize = fmt.nBlockAlign;
518 
519   AVISTREAMINFO info;
520   memset(&info, 0, sizeof(info));
521   info.fccType = streamtypeAUDIO;
522   info.dwScale = fmt.nBlockAlign;
523   info.dwRate = fmt.nAvgBytesPerSec;
524   info.dwSampleSize = fmt.nBlockAlign;
525   info.dwQuality = (DWORD)-1;
526   strcpy(info.szName, fmt.nChannels == 2 ? "Stereo Audio" : "Mixed Audio");
527 
528   if (IS_RESULT_ERROR(AVIFileCreateStream(m_file, &m_audioStream, &info), "creating AVI audio stream"))
529     return false;
530 
531   if (IS_RESULT_ERROR(AVIStreamSetFormat(m_audioStream, 0, &fmt, sizeof(fmt)), "setting format of AVI audio stream"))
532     return false;
533 
534   return m_audioMixer->AddStream(strmId);
535 }
536 
537 
OnMixedAudio(const RTP_DataFrame & frame)538 bool OpalAVIRecordManager::OnMixedAudio(const RTP_DataFrame & frame)
539 {
540   if (!IsOpen() || PAssertNULL(m_audioStream) == NULL)
541     return false;
542 
543   DWORD samples = frame.GetPayloadSize()/m_audioSampleSize;
544   if (IS_RESULT_ERROR(AVIStreamWrite(m_audioStream,
545                                    m_audioSampleCount, samples,
546                                    frame.GetPayloadPtr(), frame.GetPayloadSize(),
547                                    0, NULL, NULL), "writing AVI audio stream"))
548     return false;
549 
550   m_audioSampleCount += samples;
551   return true;
552 }
553 
554 
OpenVideo(const PString & strmId,const OpalMediaFormat &)555 bool OpalAVIRecordManager::OpenVideo(const PString & strmId, const OpalMediaFormat & /*format*/)
556 {
557   if (m_videoMixer == NULL)
558     return false;
559 
560   if (m_videoStream != NULL)
561     return m_videoMixer->AddStream(strmId);
562 
563   PTRACE(4, "OpalRecord\tCreating AVI stream for video format '" << m_options.m_videoFormat << '\'');
564 
565   PVideoFrameInfo yuv(m_options.m_videoWidth, m_options.m_videoHeight, "YUV420P");
566   PVideoFrameInfo rgb(m_options.m_videoWidth, m_options.m_videoHeight, "BGR24");
567   m_videoConverter = PColourConverter::Create(yuv, rgb);
568   if (m_videoConverter == NULL)
569     return false;
570   m_videoConverter->SetVFlipState(true);
571 
572   AVISTREAMINFO info;
573   memset(&info, 0, sizeof(info));
574   info.fccType = streamtypeVIDEO;
575   info.dwRate = 1000;
576   info.dwScale = info.dwRate/m_options.m_videoRate;
577   info.rcFrame.right = m_options.m_videoWidth;
578   info.rcFrame.bottom = m_options.m_videoHeight;
579   info.dwSuggestedBufferSize = m_options.m_videoWidth*m_options.m_videoHeight*3/2;
580   info.dwQuality = (DWORD)-1;
581   strcpy(info.szName, VideoModeNames[m_options.m_videoMixing]);
582 
583   if (IS_RESULT_ERROR(AVIFileCreateStream(m_file, &m_videoStream, &info), "creating AVI video stream"))
584     return false;
585 
586   AVICOMPRESSOPTIONS opts;
587   memset(&opts, 0, sizeof(opts));
588   opts.fccType = streamtypeVIDEO;
589   opts.fccHandler = mmioFOURCC(m_options.m_videoFormat[0],
590                                m_options.m_videoFormat[1],
591                                m_options.m_videoFormat[2],
592                                m_options.m_videoFormat[3]);
593   opts.dwQuality = (DWORD)-1;
594   if (IS_RESULT_ERROR(AVIMakeCompressedStream(&m_videoCompressor, m_videoStream, &opts, NULL), "creating AVI video compressor"))
595     return false;
596 
597   BITMAPINFOHEADER fmt;
598   memset(&fmt, 0, sizeof(fmt));
599   fmt.biSize = sizeof(fmt);
600   fmt.biCompression = BI_RGB;
601   fmt.biWidth = m_options.m_videoWidth;
602   fmt.biHeight = m_options.m_videoHeight;
603   fmt.biBitCount = 24;
604   fmt.biPlanes = 1;
605   fmt.biSizeImage = rgb.CalculateFrameBytes();
606 
607   if (IS_RESULT_ERROR(AVIStreamSetFormat(m_videoCompressor, 0, &fmt, sizeof(fmt)), "setting format of AVI video compressor"))
608     return false;
609 
610   PTRACE(4, "OpalRecord\tAllocating video buffer " << fmt.biSizeImage << " bytes");
611   return m_videoBuffer.SetSize(fmt.biSizeImage) &&
612          m_videoMixer->AddStream(strmId);
613 }
614 
615 
OnMixedVideo(const RTP_DataFrame & frame)616 bool OpalAVIRecordManager::OnMixedVideo(const RTP_DataFrame & frame)
617 {
618   if (!IsOpen() || PAssertNULL(m_videoStream) == NULL || PAssertNULL(m_videoConverter) == NULL)
619     return false;
620 
621   PluginCodec_Video_FrameHeader * header = (PluginCodec_Video_FrameHeader *)frame.GetPayloadPtr();
622   if (header->x != 0 || header->y != 0 || header->width != m_options.m_videoWidth || header->height != m_options.m_videoHeight) {
623     PTRACE(2, "OpalRecord\tUnexpected change of video frame size!");
624     return false;
625   }
626 
627   PINDEX bytesReturned = 0;
628   if (!m_videoConverter->Convert(OPAL_VIDEO_FRAME_DATA_PTR(header), m_videoBuffer.GetPointer(), &bytesReturned)) {
629     PTRACE(2, "OpalRecord\tConversion of YUV420P to RGB24 failed!");
630     return false;
631   }
632 
633   if (IS_RESULT_ERROR(AVIStreamWrite(m_videoCompressor,
634                                      m_VideoFrameCount, 1,
635                                      m_videoBuffer.GetPointer(), bytesReturned,
636                                      AVIIF_KEYFRAME, NULL, NULL), "writing AVI video stream"))
637     return false;
638 
639   ++m_VideoFrameCount;
640   return true;
641 }
642 
643 
644 #endif // _WIN32
645 
646 #endif // OPAL_HAS_MIXER
647 
648 
649 /////////////////////////////////////////////////////////////////////////////
650