/* * AviFile.cpp * * Created on: 08/mar/2022 * Author: chessaa */ #include "AviFile.h" #include #include #include #include "gtl_cmdio.h" #include //#include //Audio capture #define BITS_PER_SAMPLE 8 #define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8) const int BUFFER_DURATION = 1; const int CHANNEL_COUNT = 1; const int SAMPLE_RATE = 22050; //48000; //44100; const int DATA_SIZE = BYTES_PER_SAMPLE* SAMPLE_RATE * BUFFER_DURATION * CHANNEL_COUNT; //const int BUFFER_SIZE = 10; const int NUMBER_OF_BUFFERS = 8; const unsigned int WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE = 0x0010; AviFile* AviFile_instance; #define AVI_LOG cmdio_debug #ifdef CAPTURE_AUDIO class AudioCaptureImplementation { public: void(*callback)(void* cookie, WAVEHDR data); void* callback_cookie; bool running; MMRESULT res; HWAVEIN micHandle; WAVEFORMATEX format; WAVEHDR buffers[32]; unsigned int buffers_data[32][DATA_SIZE]; volatile uint64_t buffers_queue; bool addBuffer(WAVEHDR *buffer); static void CALLBACK waveInProc( HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2); void Update(); bool StartCapture(); void StopCapture(); void closeCapture(); CRITICAL_SECTION myCs; unsigned int time; AudioCaptureImplementation(): callback(0), callback_cookie(0), running(false), res(0), micHandle(0), buffers_queue(0), time(0) { memset(&format, 0, sizeof format); memset(buffers, 0, sizeof buffers); InitializeCriticalSection(&myCs); } virtual ~AudioCaptureImplementation() { } void lock() { EnterCriticalSection(&myCs); } void unlock() { LeaveCriticalSection(&myCs); } int flush(bool disregard=false); }; void CALLBACK AudioCaptureImplementation::waveInProc( HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { if (uMsg == WIM_DATA) { WAVEHDR* hdr = (WAVEHDR*)dwParam1; int id = hdr->dwUser; AudioCaptureImplementation& instance=*reinterpret_cast(dwInstance); instance.lock(); instance.buffers_queue<<=8; instance.buffers_queue|=(id+1); #if 0 WAVEHDR& buffer=instance.buffers[id]; //cmdio_psuccess("Audio Buffer: %d 0x%X %u samples", id, instance.buffers_queue, buffer.dwBytesRecorded); #endif instance.unlock(); } } bool AudioCaptureImplementation::addBuffer(WAVEHDR* buffer) { res = waveInPrepareHeader(micHandle, buffer, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) { cmdio_pfail("waveInPrepareHeader() failed %u", res); return false; } res = waveInAddBuffer(micHandle, buffer, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) { cmdio_pfail("waveInAddBuffer() failed %u", res); return false; } return true; } bool AudioCaptureImplementation::StartCapture() { if (micHandle==0) { memset(&format, 0, sizeof format); format.nChannels = CHANNEL_COUNT; format.cbSize = 0; format.nSamplesPerSec = SAMPLE_RATE; format.wFormatTag = WAVE_FORMAT_PCM; format.wBitsPerSample = BITS_PER_SAMPLE; format.nBlockAlign = (CHANNEL_COUNT * format.wBitsPerSample) / 8; format.nAvgBytesPerSec = SAMPLE_RATE * format.nBlockAlign; res=waveInOpen(&micHandle, WAVE_MAPPER, &format, (DWORD_PTR)waveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION|WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE); if (res != MMSYSERR_NOERROR) { cmdio_perror("waveInOpen() failed"); micHandle=0; return false; } for (int i = 0; i < NUMBER_OF_BUFFERS; i++) { WAVEHDR* header=&buffers[i]; //new WAVEHDR; ZeroMemory(header, sizeof(*header)); header->lpData = (char*)buffers_data[i]; //(LPSTR)new short int[DATA_SIZE]; header->dwBufferLength = DATA_SIZE; header->dwBytesRecorded = 0; header->dwUser = 0; header->dwFlags = 0; header->dwLoops = 0; header->dwUser = i; //buffers[i] = header; bool ok=addBuffer(&buffers[i]); if (ok) cmdio_printf(AVI_LOG, "Audio: adding buffer %d 0x%X", i, header->lpData); } } waveInStart(micHandle); return true; } void AudioCaptureImplementation::StopCapture() { if (micHandle) waveInStop(micHandle); } void AudioCaptureImplementation::closeCapture() { if (micHandle) { waveInClose(micHandle); } } int AudioCaptureImplementation::flush(bool disregard) { int one_sec=0; while(buffers_queue) { lock(); unsigned int id=buffers_queue & 0x0FF; buffers_queue>>=8; unlock(); if (id==0) continue; WAVEHDR& buffer=buffers[id-1]; cmdio_printf(AVI_LOG, "Audio::flush(): %d 0x%p %u", id, buffer.lpData, buffer.dwBytesRecorded); if (!disregard) { if (callback) callback(callback_cookie, buffer); } addBuffer(&buffer); ++one_sec; } return one_sec; } #else class AudioCaptureImplementation { public: bool addBuffer(WAVEHDR */*buffer*/) { return false; } void* callback_cookie; void(*callback)(void* cookie, WAVEHDR data); static void CALLBACK waveInProc( HWAVEIN /*hwi*/, UINT /*uMsg*/, DWORD_PTR /*dwInstance*/, DWORD_PTR /*dwParam1*/, DWORD_PTR /*dwParam2*/) { } void Update() { } bool StartCapture() { return false; } void StopCapture() { } void closeCapture() { } void lock() { //EnterCriticalSection(&myCs); } void unlock() { //LeaveCriticalSection(&myCs); } int flush(bool /*disregard=false*/) { return 0; } }; #endif class AviFile::Implementation { public: struct stream_t { bool ready; PAVISTREAM ps; PAVISTREAM psCompressed; PAVISTREAM psWrite; AVISTREAMINFO strhdr; AVICOMPRESSOPTIONS opts; BITMAPINFOHEADER bi; WAVEFORMATEX format; unsigned long time; void close() { if (ready) { ready=false; if (ps) AVIStreamClose(ps); if (psCompressed) AVIStreamClose(psCompressed); } } }; PAVIFILE avif; char fname[512]; HRESULT hr; stream_t videoStream; stream_t audioStream; stream_t textStream; AudioCaptureImplementation audioCapturer; static DWORD myFOURCC() { //return mmioFOURCC('M', 'P', '4', 'V'); //('M', 'S', 'V', 'C'); return mmioFOURCC('M','S','V','C'); } Implementation(): avif(0), hr(0) { memset(&videoStream, 0, sizeof videoStream); memset(&audioStream, 0, sizeof audioStream); memset(&textStream, 0, sizeof textStream); } bool createTextStrem_() { stream_t& s=textStream; memset(&s, 0, sizeof s); s.strhdr.fccType = streamtypeTEXT; s.strhdr.fccHandler = 0; s.strhdr.dwScale = 1; s.strhdr.dwRate = 30; s.strhdr.dwLength = 1; s.strhdr.dwQuality = 0; strcpy(s.strhdr.szName, "SUBT"); hr = AVIFileCreateStream(avif, // file pointer &s.ps, // returned stream pointer &s.strhdr); // stream header if (hr != AVIERR_OK) { cmdio_oserror("Video::AVIFileCreateStream(txts)"); return false; } s.opts.fccType = streamtypeTEXT; s.opts.dwKeyFrameEvery = 1; s.opts.dwQuality=0; s.opts.dwFlags=0; // = AVICOMPRESSF_KEYFRAMES|AVICOMPRESSF_VALID; s.opts.lpFormat=&s.format; s.opts.cbFormat=sizeof s.format; hr = AVIStreamSetFormat(s.ps, 0, &s.format, sizeof s.format); if (hr != AVIERR_OK) { cmdio_oserror("Audio::AVIStreamSetFormat()"); return false; } s.ready=true; return true; } bool createVideoStream(int rate, int rectwidth, int rectheight, int key, int quality) { stream_t& s=videoStream; memset(&s, 0, sizeof s); if (!avif) return false; DWORD buffersize=rectwidth*rectheight*sizeof(uint32_t); s.strhdr.fccType = streamtypeVIDEO;// stream type s.strhdr.fccHandler = myFOURCC(); //mmioFOURCC('M','S','V','C'); // Microsoft video 1 //strhdr.fccHandler = mmioFOURCC('I','V','5','0'); // Intel video 5.0 //strhdr.dwFlags = AVISTREAMINFO_DISABLED; //strhdr.dwCaps = //strhdr.wPriority = //strhdr.wLanguage = s.strhdr.dwScale = 10; s.strhdr.dwRate = rate; // rate fps //strhdr.dwStart = //strhdr.dwLength = //strhdr.dwInitialFrames = s.strhdr.dwSuggestedBufferSize = buffersize; s.strhdr.dwQuality = -1; // use the default strcpy(s.strhdr.szName, "video"); //strhdr.dwSampleSize = SetRect(&s.strhdr.rcFrame, 0, 0, // rectangle for stream (int) rectwidth, (int) rectheight); hr = AVIFileCreateStream(avif, // file pointer &s.ps, // returned stream pointer &s.strhdr); // stream header if (hr != AVIERR_OK) { cmdio_oserror("Video::AVIFileCreateStream()"); return false; } s.opts.fccType = streamtypeVIDEO; s.opts.fccHandler = myFOURCC(); //opts.fccHandler = 0; //opts.fccHandler = mmioFOURCC('D','I','B',' '); // Uncompressed //opts.fccHandler = mmioFOURCC('C','V','I','D'); // Cinpak //opts.fccHandler = mmioFOURCC('I','V','3','2'); // Intel video 3.2 //opts.fccHandler = mmioFOURCC('M','S','V','C'); // Microsoft video 1 //opts.fccHandler = mmioFOURCC('I','V','5','0'); // Intel video 5.0 s.opts.dwKeyFrameEvery = key; //-1; //50; s.opts.dwQuality=quality; //-1; //opts.dwBytesPerSecond s.opts.dwFlags = AVICOMPRESSF_KEYFRAMES|AVICOMPRESSF_VALID; //opts.lpFormat //opts.cbFormat //opts.lpParms //opts.cbParms //opts.dwInterleaveEvery /* display the compression options dialog box if specified compressor is unknown */ hr = AVIMakeCompressedStream(&s.psCompressed, s.ps, &s.opts, 0); if (hr != AVIERR_OK) { cmdio_oserror("Video::AVIMakeCompressedStream()"); return false; } s.bi.biHeight=rectheight; s.bi.biWidth=rectwidth; s.bi.biCompression=0; s.bi.biPlanes=1; s.bi.biBitCount=32; s.bi.biSizeImage=s.bi.biHeight*s.bi.biWidth*4; s.bi.biSize=sizeof s.bi; hr = AVIStreamSetFormat(s.psCompressed, 0, &s.bi, s.bi.biSize + s.bi.biClrUsed * sizeof(RGBQUAD)); if (hr != AVIERR_OK) { cmdio_oserror("Video::AVIStreamSetFormat()"); return false; } s.ready=true; return true; } bool addVideoFrame(const void* data) { stream_t& s=videoStream; if (!s.ready) { cmdio_pwarning("addVideoFrame(): stream not ready"); return false; } const DWORD ImageSize=s.bi.biSizeImage; hr = AVIStreamWrite(s.psCompressed, // stream pointer s.time, // time of this frame 1, // number to write const_cast(data), ImageSize, // lpbi->biSizeImage, // size of this frame s.time==0 ? AVIIF_KEYFRAME : 0, // flags.... NULL, NULL); ++s.time; if (hr != AVIERR_OK) { //CString strMsg; //strMsg.Format("Error: AVIStreamWrite, error %d",hr); cmdio_oserror("addVideoFrame()=%d",hr); return false; } return true; } bool createAudioStream(int rate, int ch, int bytes_per_sample) { stream_t& s=audioStream; memset(&s, 0, sizeof s); if (!avif) return false; s.format.nChannels=ch; s.format.nSamplesPerSec=rate; s.format.wFormatTag=WAVE_FORMAT_PCM; s.format.wBitsPerSample=bytes_per_sample*8; s.format.nBlockAlign=(ch*s.format.wBitsPerSample)/8; s.format.nAvgBytesPerSec=ch*rate*s.format.nBlockAlign; s.strhdr.fccType = streamtypeAUDIO;// stream type s.strhdr.dwScale = 1; s.strhdr.dwRate = rate; // rate fps //strhdr.dwStart = //strhdr.dwLength = //strhdr.dwInitialFrames = s.strhdr.dwSuggestedBufferSize = 0; //rate; s.strhdr.dwQuality = -1; // use the default s.strhdr.rcFrame=videoStream.strhdr.rcFrame; strcpy(s.strhdr.szName, "audio"); hr = AVIFileCreateStream(avif, // file pointer &s.ps, // returned stream pointer &s.strhdr); // stream header if (hr != AVIERR_OK) { cmdio_oserror("Audio::AVIFileCreateStream()"); return false; } s.psWrite=s.ps; s.opts.fccType = streamtypeAUDIO; s.opts.dwKeyFrameEvery = 1; s.opts.dwQuality=-1; //opts.dwBytesPerSecond s.opts.dwFlags=0; // = AVICOMPRESSF_KEYFRAMES|AVICOMPRESSF_VALID; s.opts.lpFormat=&s.format; s.opts.cbFormat=sizeof s.format; //opts.lpFormat //opts.cbFormat //opts.lpParms //opts.cbParms //opts.dwInterleaveEvery /* display the compression options dialog box if specified compressor is unknown */ #if 0 hr = AVIMakeCompressedStream(&s.psCompressed, s.ps, &s.opts, 0); if (hr != AVIERR_OK) { cmdio_oserror("Audio::AVIMakeCompressedStream()"); return false; } s.psWrite=s.psCompressed; #endif hr = AVIStreamSetFormat(s.psWrite, 0, &s.format, sizeof s.format); if (hr != AVIERR_OK) { cmdio_oserror("Audio::AVIStreamSetFormat()"); return false; } s.ready=true; audioCapturer.callback=audio_callback; audioCapturer.callback_cookie=this; return true; } bool addAudioSamples(int size, const void* data) { stream_t& s=audioStream; int numsmaples=size/(s.format.wBitsPerSample/8); if (!s.ready) { cmdio_pwarning("addAudioSamples(): stream not ready"); return false; } if (!s.psWrite) { cmdio_pfail("addAudioSamples(): no write stream"); return false; } hr = AVIStreamWrite(s.psWrite, // stream pointer s.time, // time of this frame numsmaples, // number to write const_cast(data), size, // lpbi->biSizeImage, // size of this frame 0, 0, 0); s.time+=numsmaples; if (hr != AVIERR_OK) { //CString strMsg; //strMsg.Format("Error: AVIStreamWrite, error %d",hr); cmdio_oserror("addAudioSamples()=%d",hr); return false; } return true; } static void audio_callback(void* cookie, WAVEHDR hdr) { cmdio_printf(AVI_LOG, "Audio::audio_callback(): %d", hdr.dwBytesRecorded); AviFile::Implementation* instance=reinterpret_cast(cookie); instance->addAudioSamples(hdr.dwBytesRecorded, hdr.lpData); } }; AviFile::AviFile(): p_(*new Implementation) { AVIFileInit(); } AviFile::~AviFile() { close(); delete &p_; } bool AviFile::open(const char* name, bool add_date) { if (name) { strncpy(p_.fname, name, sizeof p_.fname); } else strcpy(p_.fname, "avi_file.avi"); if (p_.avif) close(); HRESULT hr = AVIFileOpen(&p_.avif, // returned file pointer p_.fname, // file name OF_WRITE | OF_CREATE, // mode to open file with 0); // use handler determined // from file extension.... if (hr != AVIERR_OK) { cmdio_oserror("AviFile::open(%s)", p_.fname); return false; } cmdio_psuccess("AviFile::open(%s)", p_.fname); return true; } bool AviFile::isOpen() const { return p_.avif!=0; } bool AviFile::close() { p_.audioCapturer.StopCapture(); p_.videoStream.close(); p_.audioStream.close(); //addSubtitle(0, 0); if (p_.avif) { AVIFileClose(p_.avif); p_.avif=0; cmdio_psuccess("Video saved: %s", p_.fname); } return true; } bool AviFile::openVideoStream(int rate,int w, int h, int key, int quality) { return p_.createVideoStream(rate, w, h,key, quality); } bool AviFile::addVideoFrame(const void* data) { //if (!p_.textStream.ready) p_.createTextStrem(); return p_.addVideoFrame(data); } bool AviFile::openAudioStream(int rate, int ch, int bytes_per_sample) { return p_.createAudioStream(rate, ch, bytes_per_sample); } bool AviFile::openAudioStream() { return p_.createAudioStream(SAMPLE_RATE, 1, BYTES_PER_SAMPLE); } bool AviFile::addAudioSamples(int samples, const void* data) { return p_.addAudioSamples(samples, data); } bool AviFile::audioCapture(bool enable) { if (enable) return p_.audioCapturer.StartCapture(); else { p_.audioCapturer.StopCapture(); return true; } } int AviFile::audioFlush(bool disregard) { return p_.audioCapturer.flush(disregard); } AviFile* AviFile::instance() { if (AviFile_instance==0) { AviFile_instance=new AviFile; } return AviFile_instance; } static void ascii2UTF16(char* dst, const char* src) { for(int i=0; src[i]; ++i) { dst[i*2]=src[i]; dst[i*2+1]=0; } } void AviFile::addSubtitle(const char* data, unsigned int size) { return; #if 0 struct txt_header_t { unsigned char gab2[4]; unsigned char byte0; unsigned short ucode02; unsigned int name_size; char name[32]; unsigned short w04; unsigned int txt_size; char fake[2048]; } __attribute__((packed)); static txt_header_t txt; if (!p_.textStream.ready) { bool ok=p_.createTextStrem(); if (!ok) return; } txt.gab2[0]='G'; txt.gab2[1]='A'; txt.gab2[2]='B'; txt.gab2[3]='2'; txt.ucode02=2; txt.name_size=0; txt.txt_size=1024; txt.w04=0x04; txt.name_size=sizeof txt.name; ascii2UTF16(txt.name, "TXT"); ascii2UTF16(txt.fake, "1\n" "00::00:10,000 --> 00:00:24,400\n" "prova prova!!!\n\n"); LONG sampleWritten=0; LONG bytesWritten=0; AVIStreamWrite(p_.textStream.ps, 0, 1, &txt, sizeof txt, 0, &sampleWritten, &bytesWritten); cmdio_pinfo("txt %d %d", sampleWritten, bytesWritten); p_.textStream.close(); #endif } void AviFile::addSubtitleInstance(const char* data, unsigned int size) { if (!AviFile_instance) return; AviFile_instance->addSubtitle(data, size); }