Запись изображения и звука в avi файл, с помощью API MS Windows
Автор: Игорь Спиридонов
В данном документе рассматривается запись видео и аудио данных в avi файл, с помощью библиотеки «AviFile» операционной системы Microsoft Windows. Данный файл является контейнером звуковых и видео данных. Конкретный формат зависит от применяемого кодека. Рассмотрим по порядку этапы которые необходимы для записи.
1. Подготовка
2. Выбор видео кодека
3. Создание avi файла
4. Создание видео потока
5. Создание видео потока с компрессией
6. Создание аудио потока
7. Запись видео данных
8. Запись аудио данных
9. Очистка ресурсов
10. Исходник
1. Подготовка
Подключаем необходимые заголовочные файлы и библиотеки.
#include <windows.h> #include <vfw.h> #pragma comment (lib, "vfw32")
2. Выбор видео кодека
Необходимо заполнить структуру AVICOMPRESSOPTIONS, которая содержит информацию о потоке, как его сжимать и сохранять. Ниже пример функции.
bool ChooseCodec(HWND hWnd, AVICOMPRESSOPTIONS *pOptionsVideo) { COMPVARS cv; //Настройки кодека ZeroMemory( &cv, sizeof( cv)); cv.cbSize = sizeof( cv); //Выбор кодека if( ICCompressorChoose( hWnd, ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME, NULL, NULL, &cv, ( "Выбор и настройка кодека"))) { ZeroMemory( pOptionsVideo, sizeof( AVICOMPRESSOPTIONS)); //Заполняем структуру AVICOMPRESSOPTIONS pOptionsVideo->fccType = streamtypeVIDEO; //тип потока. pOptionsVideo->fccHandler = cv.fccHandler; //четырёхсимвольный код кодека. //Если были выбраны ключевые кадры, //то заполняем данное поле и устанавливаем соответствующий флаг. if( cv.lKey) { pOptionsVideo->dwKeyFrameEvery = cv.lKey; pOptionsVideo->dwFlags |= AVICOMPRESSF_KEYFRAMES; } //Заполняем поле качества потока. pOptionsVideo->dwQuality = cv.lQ; //Если был выбран битрейт, то заполняем это поле и устанавливаем флаг. if( cv.lDataRate) { pOptionsVideo->dwFlags |= AVICOMPRESSF_DATARATE; pOptionsVideo->dwBytesPerSecond = cv.lDataRate * 1024; } //Незабываем освободить ресурсы полученные через ICCompressorChoose. ICCompressorFree( &cv); return true; } return false; }
ICCompressorChoose отображает диалоговое окно выбора и настроек видео кодека, а также заполняет структуру COMPVARS, содержащую эти настройки.
BOOL ICCompressorChoose( HWND hwnd, UINT uiFlags, LPVOID pvIn, LPVOID lpData, PCOMPVARS pc, LPSTR lpszTitle );
wnd - хендл родительского окна, диалога выбора кодека.
uiFlags - применяемые флаги. ICMF_CHOOSE_DATARATE - отображать переключатель и поле ввода для битрейта данных. ICMF_CHOOSE_KEYFRAME - отображать переключатель и поле ввода для частоты ключевых кадров.
pvIn и lpData - не используем.
pc - указатель на структуру COMPVARS, после успешного вызова она будет содержать настройки кодека.
lpszTitle - опциональный параметр - имя диалога.
3. Создание avi файла
PAVIFILE pAviFile; //Указатель на интерфейс avi файла //Инициализируем библиотеку AviFile AVIFileInit(); //Пытаемcя создать avi файл. if ( AVIERR_OK!=AVIFileOpen( &pAviFile, lpszFileName, OF_WRITE | OF_CREATE, NULL) ) return 0;
Открытие avi файла.
STDAPI AVIFileOpen( PAVIFILE *ppfile, LPCTSTR szFile, UINT mode, CLSID *pclsidHandler );
ppfile - указатель на указатель интерфейса avi файла.
szFile - имя файла.
mode - режим открытия файла, нам нужно создать новый для записи - OF_WRITE | OF_CREATE
pclsidHandler - не используем.
Если эта функция вернула значение отличное от AVIERR_OK, значит ошибка.
4. Создание видео потока
PAVISTREAM pStreamVideo; //Объявляем указатель на видео поток. bool CreateVideoStream(DWORD dwFrameRate, size_t WidthFrame, size_t HeightFrame, PAVISTREAM *ppStreamVideo) { //Объявляем и обнуляем структуру AVISTREAMINFO. AVISTREAMINFO sStreamInfo; ZeroMemory( &sStreamInfo, sizeof( sStreamInfo)); //Заполняем поля: тип потока, масштаб, количество кадров в секунду и //предполагаемый размер буфера кадра в байтах. //Если размер буфера заранее не знаем, то можно в это поле занести ноль. sStreamInfo.fccType = streamtypeVIDEO; sStreamInfo.dwScale = 1; sStreamInfo.dwRate = dwFrameRate; sStreamInfo.dwSuggestedBufferSize = 0; //Заполняем структуру sStreamInfo.rcFrame размерами кадра в пикселях. SetRect( &sStreamInfo.rcFrame, 0, 0, WidthFrame, HeightFrame); //Создаём видео поток. return ( AVIERR_OK==AVIFileCreateStream( pAviFile, ppStreamVideo, &sStreamInfo)); }
Создание видео потока.
STDAPI AVIFileCreateStream( PAVIFILE pfile, PAVISTREAM *ppavi, AVISTREAMINFO *psi );
pfile - указатель на avi интерфейс.
ppavi - указатель на указатель интерфейса видео потока.
psi - указатель на структуру AVISTREAMINFO.
5. Создание видео потока с компрессией
//pStreamVideo - указатель на видео поток полученный на предыдущем шаге. bool MakeCompressedVideoStream(size_t WidthFrame, size_t HeightFrame, AVICOMPRESSOPTIONS *pVideoOptions, PAVISTREAM pStreamVideo, PAVISTREAM *ppCompressedStreamVideo) { //Заполняем структуру BITMAPINFO BITMAPINFO bi; ZeroMemory( &bi, sizeof( BITMAPINFO)); bi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER); //размер структуры bi.bmiHeader.biWidth = WidthFrame; //ширина кадра в пикселях. bi.bmiHeader.biHeight = HeightFrame; //высота кадра в пикселях. //размер кадра в байтах: bi.bmiHeader.biSizeImage = WidthFrame * HeightFrame * 3; bi.bmiHeader.biPlanes = 1; //количество цветовых плоскостей, у нас 1. bi.bmiHeader.biBitCount = 24; //количество бит на пиксель. bi.bmiHeader.biCompression = BI_RGB; //формат, у нас RGB. // Вычисляем указатель на массив пикселей, // нужно для определения размера формата. void *pBits = ( ( LPBYTE)&bi) + bi.bmiHeader.biSize + bi.bmiHeader.biClrUsed * sizeof( RGBQUAD); //Пытаемся создать поток с компрессией. if( AVIERR_OK==AVIMakeCompressedStream( ppCompressedStreamVideo, pStreamVideo, pVideoOptions, NULL) ) { //Устанавливаем формат. if ( AVIERR_OK == AVIStreamSetFormat( *ppCompressedStreamVideo, 0, &bi, ( ( LPBYTE)pBits) - ( ( LPBYTE) &bi))) return true; //В случае неуспеха удаляем поток. AVIStreamRelease( *ppCompressedStreamVideo); pCompressedStreamVideo = 0; } //Если предыдущий вызов AVIStreamSetFormat завершился неудачей, //то пытаемся установить другой формат. 16 бит на пиксель. bi.bmiHeader.biBitCount = 16; if( AVIERR_OK==AVIMakeCompressedStream( ppCompressedStreamVideo, pStreamVideo, pVideoOptions, NULL) ) { //Устанавливаем формат. if ( AVIERR_OK == AVIStreamSetFormat( *ppCompressedStreamVideo, 0, &bi, ( ( LPBYTE)pBits) - ( ( LPBYTE) &bi))) return true; //В случае неуспеха удаляем поток. AVIStreamRelease( *ppCompressedStreamVideo); pCompressedStreamVideo = 0; } //Если предыдущий вызов AVIStreamSetFormat завершился неудачей, //то пытаемся установить другой формат. 8 бит на пиксель. bi.bmiHeader.biBitCount = 8; if( AVIERR_OK==AVIMakeCompressedStream( ppCompressedStreamVideo, pStreamVideo, pVideoOptions, NULL) ) { //Устанавливаем формат. if ( AVIERR_OK == AVIStreamSetFormat( *ppCompressedStreamVideo, 0, &bi, ( ( LPBYTE)pBits) - ( ( LPBYTE) &bi))) return true; //В случае неуспеха удаляем поток. AVIStreamRelease( *ppCompressedStreamVideo); pCompressedStreamVideo = 0; } return false; }
Создание потока с компрессией.
STDAPI AVIMakeCompressedStream( PAVISTREAM *ppsCompressed, PAVISTREAM psSource, AVICOMPRESSOPTIONS *lpOptions, CLSID *pclsidHandler );
ppsCompressed - указатель на указатель интерфейса видео потока со сжатием.
psSource - указатель на видео поток без сжатия.
lpOptions - указатель на структуру AVICOMPRESSOPTIONS, которая была сформирована ранее посредством выбора кодека.
pclsidHandler - не используем.
Установка формата.
STDAPI AVIStreamSetFormat( PAVISTREAM pavi, LONG lPos, LPVOID lpFormat, LONG cbFormat );
pavi - указатель на поток для которого устанавливается формат.
lPos - не используем.
lpFormat - указатель на формат, структуру BITMAPINFO.
сbFormat - размер формата, у нас разница указателей.
6. Создание аудио потока
Сначала необходимо заполнить структуру WAVEFORMATEX.
WAVEFORMATEX wf; ZeroMemory(&wf, sizeof( WAVEFORMATEX)); wf.cbSize = sizeof( WAVEFORMATEX); //размер структуры WAVEFORMATEX. wf.wFormatTag = WAVE_FORMAT_PCM; //формат, у нас pcm. wf.nChannels = 2; //кол-во каналов: 1 - моно, 2 - стерео. wf.nSamplesPerSec = 44100; //частота звука, герц в секунду. wf.wBitsPerSample = 16; //количество бит за один квант на одном канале. wf.nBlockAlign = ( wf.wBitsPerSample * wf.nChannels) / 8; // байт за один квант. wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; // кол-во байт в секунду.
Создаём аудио поток, передавая указатель на заполненную структуру WAVEFORMATEX
bool CreateAudioStream(WAVEFORMATEX *pwf, PAVIFILE pAviFile, PAVISTREAM *ppStreamAudio) { //Заполняем структуру AVISTREAMINFO согласно параметрам звука. AVISTREAMINFO sStreamInfo; ZeroMemory( &sStreamInfo, sizeof( sStreamInfo)); sStreamInfo.fccType = streamtypeAUDIO; //тип потока. sStreamInfo.dwScale = pwf->nBlockAlign; //кол-во байт за один квант на всех каналах. sStreamInfo.dwRate = pwf->nSamplesPerSec * sStreamInfo.dwScale; // байт в сек. sStreamInfo.dwInitialFrames = 1; //номер первого кадра. sStreamInfo.dwSampleSize = pwf->nBlockAlign; //кол-во байт за квант на всех каналах. sStreamInfo.dwQuality = ( DWORD)-1; //качество, -1 - по умолчанию. //Cоздаём поток и устанавливаем формат, // аналогично тому как это было для видеопотока. if( AVIERR_OK!=AVIFileCreateStream( pAviFile, ppStreamAudio, &sStreamInfo) ) return false; return ( AVIERR_OK==AVIStreamSetFormat( *ppStreamAudio, 0, ( void*)pwf, sizeof( WAVEFORMATEX))); }
7. Запись видео данных
Функция пишет один кадр в видео поток.
bool WriteFrameVideoCompress(PAVISTREAM pCompressedStreamVideo, BITMAPINFO *pBmp, DWORD dwFrameNum) { return ( ( AVIERR_OK==AVIStreamWrite( pCompressedStreamVideo, dwFrameNum, 1, ( ( LPBYTE)pBmp) + pBmp->bmiHeader.biSize + pBmp->bmiHeader.biClrUsed*sizeof( RGBQUAD), pBmp->bmiHeader.biSizeImage, 0, NULL, NULL))); }
Запись в поток.
STDAPI AVIStreamWrite( PAVISTREAM pavi, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, DWORD dwFlags, LONG *plSampWritten, LONG *plBytesWritten );
pavi - указатель на поток.
lStart - номер записываемого кадра.
lSamples - количество записываемых кадров, мы пишем по-одному кадру.
lpBuffer - указатель на буфер с данными кадра. Обратите внимание, что впереди этих данных обязательно должен быть корректно заполненный заголовок BITMAPINFOHEADER, иначе запись не произойдёт.
сbBuffer - размер в байтах буфера передаваемого в lpBuffer.
dwFlags - не используем.
plSampWritten - не используем.
plBytesWritten - не используем.
8. Запись аудио данных
Запись звука аналогична записи видео, с той лишь разницей, что впереди данных передаваемых в поле lpBuffer не требуется структура WAVEFORMATEX.
bool WriteFrameAudio(PAVISTREAM pStreamAudio, void *pData, size_t sizeData, DWORD dwFrameNum) { return ( AVIERR_OK==AVIStreamWrite( pStreamAudio, dwFrameNum, 1, pData, sizeData, 0, NULL, NULL)); }
9. Очистка ресурсов
В конце работы необходимо освободить все ресурсы. Если этого не сделать, то файл не запишется на диск.
bool AviClean() { //Освобождаем видео поток. if ( pStreamVideo) { AVIStreamRelease( pStreamVideo); pStreamVideo = 0; } //Освобождаем видео поток с компрессией. if ( pCompressedStreamVideo) { AVIStreamRelease ( pCompressedStreamVideo); pCompressedStreamVideo = 0; } //Освобождаем аудио поток. if ( pStreamAudio) { AVIStreamRelease ( pStreamAudio); pStreamAudio = 0; } //Освобождаем интерфейс Avi файла. if ( pAviFile) { AVIFileRelease( pAviFile); pAviFile = 0; } //Освобождаем AviFile библиотеку. AVIFileExit( ); }
10. Исходник
Тестовая программа(с исходником) записывающая белый шум: Source AviRecord
21 июня 2009 (Обновление: 10 сен 2009)
Комментарии [4]