Index: configure =================================================================== --- configure (revision 21487) +++ configure (working copy) @@ -95,6 +95,8 @@ --enable-pthreads use pthreads [no] --enable-w32threads use Win32 threads [no] --enable-x11grab enable X11 grabbing [no] + --enable-grabdc enable GDI grabbing [no] + --enable-wavein enable MMSystem audio grabbing [no] --disable-network disable network support [no] --disable-mpegaudio-hp faster (but less accurate) MPEG audio decoding [no] --enable-gray enable full grayscale support (slower color) Index: libavdevice/alldevices.c =================================================================== --- libavdevice/alldevices.c (revision 21487) +++ libavdevice/alldevices.c (working copy) @@ -48,6 +48,9 @@ REGISTER_INDEV (V4L, v4l); REGISTER_INDEV (VFWCAP, vfwcap); REGISTER_INDEV (X11_GRAB_DEVICE, x11_grab_device); + + REGISTER_INDEV (GRABDC, grabdc); + REGISTER_INDEV (WAVEIN, wavein); /* external libraries */ REGISTER_INDEV (LIBDC1394, libdc1394); Index: libavdevice/grabdc.c =================================================================== --- libavdevice/grabdc.c (revision 0) +++ libavdevice/grabdc.c (revision 0) @@ -0,0 +1,302 @@ +/* + * GrabDC + * Copyright (c) 2010 Alexander Nusov gmail.com> + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "libavformat/avformat.h" +#include + + + +/** + * Converts BITMAP to DIB (from MSDN Examples) + * + * @param hbitmap BITMAP + * @param bits depth + */ +HANDLE +BitmapToDIB (HBITMAP hbitmap, UINT bits) +{ + HANDLE hdib ; + HDC hdc ; + BITMAP bitmap ; + UINT wLineLen ; + DWORD dwSize ; + DWORD wColSize ; + LPBITMAPINFOHEADER lpbi ; + LPBYTE lpBits ; + + GetObject(hbitmap,sizeof(BITMAP),&bitmap) ; + + // + // DWORD align the width of the DIB + // Figure out the size of the colour table + // Calculate the size of the DIB + // + wLineLen = (bitmap.bmWidth*bits+31)/32 * 4; + wColSize = sizeof(RGBQUAD)*((bits <= 8) ? 1<biSize = sizeof(BITMAPINFOHEADER) ; + lpbi->biWidth = bitmap.bmWidth ; + lpbi->biHeight = bitmap.bmHeight ; + lpbi->biPlanes = 1 ; + lpbi->biBitCount = (WORD) bits ; + lpbi->biCompression = BI_RGB ; + lpbi->biSizeImage = dwSize - sizeof(BITMAPINFOHEADER) - wColSize ; + lpbi->biXPelsPerMeter = 0 ; + lpbi->biYPelsPerMeter = 0 ; + lpbi->biClrUsed = (bits <= 8) ? 1<biClrImportant = 0 ; + + // + // Get the bits from the bitmap and stuff them after the LPBI + // + lpBits = (LPBYTE)(lpbi+1)+wColSize ; + + hdc = CreateCompatibleDC(NULL) ; + + GetDIBits(hdc,hbitmap,0,bitmap.bmHeight,lpBits,(LPBITMAPINFO)lpbi, DIB_RGB_COLORS); + + lpbi->biClrUsed = (bits <= 8) ? 1<priv_data; + enum PixelFormat input_pixfmt; + + HDC hdc; + HDC hd; + + AVStream *st = NULL; + + int bpp; + int x_offset = 0; + int y_offset = 0; + + int cx; + int cy; + + char *param, *offset; + + /* Get parameters */ + param = av_strdup(s1->filename); + offset = strchr(param, '+'); + + if (offset) { + sscanf(offset, "%d,%d", &x_offset, &y_offset); + *offset = 0; + } + + /* Checking parameters */ + if (!ap || ap->width <= 0 || ap->height <= 0 || ap->time_base.den <= 0) { + av_log(s1, AV_LOG_ERROR, "Video size and/or rate is not defined. Use -s/-r\n"); + return AVERROR(EIO); + } + + /* Get screen metrics */ + cx = GetSystemMetrics(SM_CXSCREEN); + cy = GetSystemMetrics(SM_CYSCREEN); + + /* Check input position/size */ + if (x_offset < 0 || (x_offset + ap->width) > cx) { + av_log(s1, AV_LOG_ERROR, "Bad X coordinate or width.\n"); + return AVERROR(EIO); + } + + if (y_offset < 0 || (y_offset + ap->height) > cy) { + av_log(s1, AV_LOG_ERROR, "Bad Y coordinate or height.\n"); + return AVERROR(EIO); + } + + /* Bytes per pixel and pixelformat */ + bpp = 32; + + /* Create stream and set parameters */ + st = av_new_stream(s1, 0); + if (!st) { + return AVERROR(ENOMEM); + } + + av_set_pts_info(st, 64, 1, 1000000); + + grabdc->hdc = hdc; + grabdc->hd = hd; + grabdc->frame_size = ap->width * ap->height * bpp/8; + grabdc->frame_num = 0; + grabdc->frame_bpp = bpp; + grabdc->time_base = ap->time_base; + grabdc->time_frame = av_gettime() / av_q2d(ap->time_base); + grabdc->width = ap->width; + grabdc->height = ap->height; + grabdc->x_offset = x_offset; + grabdc->y_offset = y_offset; + + st->codec->codec_type = CODEC_TYPE_VIDEO; + st->codec->codec_id = CODEC_ID_RAWVIDEO; + st->codec->width = ap->width; + st->codec->height = ap->height; + st->codec->pix_fmt = PIX_FMT_RGB32; + st->codec->time_base = ap->time_base; + + return 0; +} + + +/** + * Grabs a frame from GrabDC + * + * @param s1 Context from avformat core + * @param pkt Packet + */ +static int +grabdc_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + struct grabdc_ctx *s = s1->priv_data; + int64_t curtime, delay; + + /* Calculate the time of the next frame */ + s->time_frame += INT64_C(1000000); + + /* wait based on the frame rate */ + for(;;) { + curtime = av_gettime(); + delay = s->time_frame * av_q2d(s->time_base) - curtime; + if (delay <= 0) { + if (delay < INT64_C(-1000000) * av_q2d(s->time_base)) { + s->time_frame += INT64_C(1000000); + } + break; + } + Sleep(0); + } + + if (av_new_packet(pkt, s->frame_size) < 0) { + return AVERROR(EIO); + } + + pkt->pts = curtime; + + int width = s->width; + int height = s->height; + int left = s->x_offset; + int top = s->y_offset; + + HDC hScreenDC = GetDC(NULL); + HDC hMemDC = CreateCompatibleDC(hScreenDC); + + HBITMAP hbm; + HBITMAP oldbm; + + hbm = CreateCompatibleBitmap(hScreenDC, width, height); + oldbm = (HBITMAP) SelectObject(hMemDC, hbm); + + StretchBlt(hMemDC, 0, 0, width, height, hScreenDC, left, top+height, width, -height, SRCCOPY); + SelectObject(hMemDC,oldbm); + + LPBITMAPINFOHEADER alpbi = (LPBITMAPINFOHEADER)GlobalLock(BitmapToDIB(hbm, s->frame_bpp)); + + memcpy(pkt->data, (LPBYTE) alpbi + alpbi->biSize + alpbi->biClrUsed * sizeof(RGBQUAD), alpbi->biSizeImage); + + GlobalFree(alpbi); + + DeleteObject(hbm); + DeleteDC(hMemDC); + + ReleaseDC(NULL, hScreenDC); + + return s->frame_size; +} + + +/** + * Close GrabDC + * + * @param s1 Context from avformat core + */ +static int +grabdc_read_close(AVFormatContext *s1) +{ + struct grabdc_ctx *grabdc = s1->priv_data; + return 0; +} + + +/** + * GrabDC demuxer declaration + */ +AVInputFormat grabdc_demuxer = { + "grabdc", + NULL_IF_CONFIG_SMALL("GrabDC Video Capture"), + sizeof(struct grabdc_ctx), + NULL, + grabdc_read_header, + grabdc_read_packet, + grabdc_read_close, + .flags = AVFMT_NOFILE, +}; Index: libavdevice/Makefile =================================================================== --- libavdevice/Makefile (revision 21487) +++ libavdevice/Makefile (working copy) @@ -21,6 +21,8 @@ OBJS-$(CONFIG_V4L_INDEV) += v4l.o OBJS-$(CONFIG_VFWCAP_INDEV) += vfwcap.o OBJS-$(CONFIG_X11_GRAB_DEVICE_INDEV) += x11grab.o +OBJS-$(CONFIG_GRABDC_INDEV) += grabdc.o +OBJS-$(CONFIG_WAVEIN_INDEV) += wavein.o # external libraries OBJS-$(CONFIG_LIBDC1394_INDEV) += libdc1394.o Index: libavdevice/wavein.c =================================================================== --- libavdevice/wavein.c (revision 0) +++ libavdevice/wavein.c (revision 0) @@ -0,0 +1,550 @@ +/* + * WaveIn - audio capture for windows + * Copyright (c) 2010 Alexander Nusov gmail.com> + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "libavformat/avformat.h" +#include +#include + + +#define WAVEIN_CAST(x) (struct wavein_data *)x +#define WAVEIN_RET_VAL_IF_NULL(x,y) if ((x) == NULL) return (y) +#define WAVEIN_RET_IF_NULL(x) if ((x) == NULL) return + +#define WAVEIN_OPENED 1 +#define WAVEIN_CLOSED 0 + +#define WAVEIN_CODEC_ID CODEC_ID_PCM_S16LE +#define WAVEIN_BITS_PER_SAMPLE 16 + +/** + * Denominator for a PCM buffer + */ +#ifndef WAVEIN_DENOMINATOR +#define WAVEIN_DENOMINATOR 1 +#endif + +/** + * Defines for missed from MinGW + */ +#ifndef WAVE_FORMAT_48M16 +#define WAVE_FORMAT_48M16 0x00004000 +#endif + +#ifndef WAVE_FORMAT_48S16 +#define WAVE_FORMAT_48S16 0x00008000 +#endif + +#ifndef WAVE_FORMAT_96M16 +#define WAVE_FORMAT_96M16 0x00040000 +#endif + +#ifndef WAVE_FORMAT_96S16 +#define WAVE_FORMAT_96S16 0x00080000 +#endif + + +/** + * Forward declarations + */ +static int wavein_close (AVFormatContext *); +static int wavein_read_close (AVFormatContext *); + + +/** + * wavein device demuxer context + */ +struct wavein_data { + HANDLE wavein_mutex; /**< waveIn Mutex */ + HANDLE wavein_event; /**< waveIn Event */ + + HWAVEIN hwi; /**< Input device ID */ + WAVEHDR wavehdr; /**< PCM data buffer */ + + int dev_id; /**< Device ID (int) */ + int state; /**< Device state */ + int recording; /**< Recording state */ + + int sample_rate; /**< Sampling rate */ + int channels; /**< Audio channels */ + + enum CodecID codec_id; /**< PCM codec identifier */ + unsigned int bufsize; /**< PCM buffer size */ + AVPacketList *pktl; /**< List of packets */ +}; + +/** + * Check audio device capabilities + * + * @param formats Available formats + * @param ar sampling rate + * @param ac channels + * @return if success - 1 + */ +static int +wavein_check_capabilities(int32_t formats, int ar, int ac) +{ + if (ar == 11025 && ac == 1 && (formats & WAVE_FORMAT_1M16)) { + return TRUE; + } + + if (ar == 11025 && ac == 2 && (formats & WAVE_FORMAT_1S16)) { + return TRUE; + } + + if (ar == 22050 && ac == 1 && (formats & WAVE_FORMAT_2M16)) { + return TRUE; + } + + if (ar == 22050 && ac == 2 && (formats & WAVE_FORMAT_2S16)) { + return TRUE; + } + + if (ar == 44100 && ac == 1 && (formats & WAVE_FORMAT_4M16)) { + return TRUE; + } + + if (ar == 44100 && ac == 2 && (formats & WAVE_FORMAT_4S16)) { + return TRUE; + } + + if (ar == 48000 && ac == 1 && (formats & WAVE_FORMAT_48M16)) { + return TRUE; + } + + if (ar == 48000 && ac == 2 && (formats & WAVE_FORMAT_48S16)) { + return TRUE; + } + + if (ar == 96000 && ac == 1 && (formats & WAVE_FORMAT_96M16)) { + return TRUE; + } + + if (ar == 96000 && ac == 2 && (formats & WAVE_FORMAT_96S16)) { + return TRUE; + } + + return FALSE; +} + + +/** + * Find all devices. + * + * @param s1 Context from avformat core + */ +static void +wavein_findalldevs(AVFormatContext *s1) +{ + unsigned int i, alldevs; + WAVEINCAPS wic; + MMRESULT mmres; + + av_log(s1, AV_LOG_INFO, "Audio devices:\n"); + + alldevs = waveInGetNumDevs(); + if (alldevs < 1) { + av_log(s1, AV_LOG_INFO, "No devices found.\n"); + } + + for (i = 0; i < alldevs; i++) { + ZeroMemory(&wic, sizeof(WAVEINCAPS)); + mmres = waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS)); + + if (mmres == MMSYSERR_NOERROR) { + av_log(s1, AV_LOG_INFO, "%d - %s\n", i, wic.szPname); + } + } +} + + +/** + * WaveIn callback + * + * @param hwi Waveform-audio input device + * @param message Notification message + * @param dwInstance User data (handle to wavein_data) + * @param dwParam1 Parameter for a message + * @param dwParam2 Parameter for a message + */ +void CALLBACK +wavein_callback(HWAVEIN hwi, UINT message, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + struct wavein_data *self = WAVEIN_CAST(dwInstance); + AVPacketList **ppktl, *pktl_next; + WAVEHDR *pwavehdr = NULL; + MMRESULT mmres; + + switch (message) + { + case WIM_OPEN: + self->state = WAVEIN_OPENED; + break; + + case WIM_DATA: + if (self->recording) { + WaitForSingleObject(self->wavein_mutex, INFINITE); + } + + pwavehdr = (WAVEHDR *) dwParam1; + + if (WHDR_DONE == (pwavehdr->dwFlags & WHDR_DONE)) { + pktl_next = av_mallocz(sizeof(AVPacketList)); + + if (pktl_next == NULL) { + ReleaseMutex(self->wavein_mutex); + return; + } + + if (av_new_packet(&pktl_next->pkt, pwavehdr->dwBufferLength) < 0) { + av_free(pktl_next); + ReleaseMutex(self->wavein_mutex); + return; + } + + self->bufsize += pwavehdr->dwBufferLength; + + pktl_next->pkt.pts = av_gettime(); + memcpy(pktl_next->pkt.data, pwavehdr->lpData, pwavehdr->dwBufferLength); + + for (ppktl = &self->pktl; *ppktl; ppktl = &(*ppktl)->next); + *ppktl = pktl_next; + + if (self->recording) { + mmres = waveInAddBuffer(self->hwi, pwavehdr, sizeof(WAVEHDR)); + } + + + SetEvent(self->wavein_event); + } + + //SetEvent(self->wavein_event); + ReleaseMutex(self->wavein_mutex); + break; + + case WIM_CLOSE: + self->state = WAVEIN_CLOSED; + break; + } +} + + + +/** + * Open input audio device. + * + * @param s1 Context from avformat core + * @param ap Parameters from avformat core + * @return 0 if success + */ +static int +wavein_open(AVFormatContext *s1, AVFormatParameters *ap) +{ + struct wavein_data *self = WAVEIN_CAST(s1->priv_data); + char *param, *ps; + int dev_id = 0; + int den = 0; + int compat = 1; + + MMRESULT mmres; + WAVEINCAPS wic = {0}; + WAVEFORMATEX wfx = {0}; + + /* Get device id/den from filename, otherwise use default */ + param = av_strdup(s1->filename); + if (strcmp(param, "alldevs") == 0) { + wavein_findalldevs(s1); + return AVERROR(EIO); + } + + if (strstr(param, ":nocaps")) { + compat = 0; + } + + ps = strrchr(param, ':'); + if (ps) { + sscanf(ps, ":%d", &den); + *ps = 0; + } + + if (strcmp(param, "mapper") == 0) { + dev_id = WAVE_MAPPER; + } + else { + dev_id = atoi(param); + } + + /* Set default denominator if needed */ + if (den == 0) { + den = WAVEIN_DENOMINATOR; + } + + /* Get device capabilities */ + mmres = waveInGetDevCaps(dev_id, &wic, sizeof(WAVEINCAPS)); + if (mmres != MMSYSERR_NOERROR) { + wavein_findalldevs(s1); + + av_log(s1, AV_LOG_ERROR, "Cannot get device capabilities.\n"); + return AVERROR(EINVAL); + } + + /* Check audio modes */ + if (!wavein_check_capabilities(wic.dwFormats, ap->sample_rate, ap->channels)) { + av_log(s1, AV_LOG_ERROR, "Audio mode is not supported.\n"); + return AVERROR(EINVAL); + } + + + /* Fill WAVEFORMATEX */ + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.wBitsPerSample = WAVEIN_BITS_PER_SAMPLE; + wfx.nChannels = ap->channels; + wfx.nSamplesPerSec = ap->sample_rate; + wfx.nBlockAlign = wfx.nChannels * WAVEIN_BITS_PER_SAMPLE/8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + wfx.cbSize = sizeof(WAVEFORMATEX); + + + /* Open device */ + mmres = waveInOpen(&self->hwi, dev_id, &wfx, (DWORD_PTR)wavein_callback, (DWORD_PTR)self, CALLBACK_FUNCTION); + if (mmres != MMSYSERR_NOERROR) { + av_log(s1, AV_LOG_ERROR, "Cannot open device.\n"); + return AVERROR(EINVAL); + } + + /* Prepare buffer */ + self->wavehdr.lpData = (LPSTR)HeapAlloc(GetProcessHeap(), 8, wfx.nAvgBytesPerSec / den); + self->wavehdr.dwBufferLength = wfx.nAvgBytesPerSec / den; + self->wavehdr.dwUser = 0; + + mmres = waveInPrepareHeader(self->hwi, &self->wavehdr, sizeof(WAVEHDR)); + if (mmres != MMSYSERR_NOERROR) { + av_log(s1, AV_LOG_ERROR, "Cannot prepare buffer.\n"); + + wavein_read_close(s1); + return AVERROR(EINVAL); + } + + /* Add buffer */ + mmres = waveInAddBuffer(self->hwi, &self->wavehdr, sizeof(WAVEHDR)); + if (mmres != MMSYSERR_NOERROR) { + av_log(s1, AV_LOG_ERROR, "Cannot add buffer.\n"); + + wavein_read_close(s1); + return AVERROR(EINVAL); + } + + + /* Set wavein data structure */ + self->dev_id = dev_id; + self->codec_id = WAVEIN_CODEC_ID; + self->channels = ap->channels; + self->sample_rate = ap->sample_rate; + + self->bufsize = 0; + self->state = 0; + self->recording = 1; + + return 0; +} + + +/** + * Close audio device + * + * @param Context from avformat core + * @return 0 if success + */ +static int +wavein_close(AVFormatContext *s1) +{ + struct wavein_data *self = WAVEIN_CAST(s1->priv_data); + MMRESULT mmres; + + self->recording = 0; + Sleep(0); + + if (self->hwi) { + mmres = waveInStop(self->hwi); + if (mmres != MMSYSERR_NOERROR) { + av_log(s1, AV_LOG_ERROR, "Cannot stop device.\n"); + } + + mmres = waveInClose(self->hwi); + if (mmres != MMSYSERR_NOERROR) { + av_log(s1, AV_LOG_ERROR, "Cannot close device.\n"); + } + } + + if (self->wavein_mutex) { + CloseHandle(self->wavein_mutex); + } + + if (self->wavein_event) { + CloseHandle(self->wavein_event); + } + + return 0; +} + + +/** + * Read the format header and initialize the AVFormatContext structure. + * + * @param s1 Context from avformat core + * @param ap Parameters from avformat core + * @return 0 if success + */ +static int +wavein_read_header(AVFormatContext *s1, AVFormatParameters *ap) +{ + struct wavein_data *self = WAVEIN_CAST(s1->priv_data); + AVStream *st = NULL; + + /* Checking parameters */ + if (!ap || ap->sample_rate <= 0 || ap->channels <= 0) { + av_log(s1, AV_LOG_ERROR, "Sampling rate and/or channels are not defined.\n"); + return AVERROR(EIO); + } + + /* Create stream */ + st = av_new_stream(s1, 0); + if (st == NULL) { + av_log(s1, AV_LOG_ERROR, "Cannot add stream.\n"); + return AVERROR(ENOMEM); + } + + /* Opening audio device */ + if (wavein_open(s1, ap)) { + return AVERROR(EIO); + } + + /* Set codecs parameters */ + st->codec->codec_type = CODEC_TYPE_AUDIO; + st->codec->codec_id = self->codec_id; + st->codec->sample_rate = self->sample_rate; + st->codec->channels = self->channels; + + av_set_pts_info(st, 32, 1, 1000); + + /* Create mutex/event */ + self->wavein_mutex = CreateMutex(NULL, 0, NULL); + if (self->wavein_mutex == NULL) { + av_log(s1, AV_LOG_ERROR, "Cannot create mutex.\n"); + + wavein_read_close(s1); + return AVERROR(EIO); + } + + self->wavein_event = CreateEvent(NULL, 1, 0, NULL); + if (self->wavein_event == NULL) { + av_log(s1, AV_LOG_ERROR, "Cannot create event.\n"); + + wavein_read_close(s1); + return AVERROR(EIO); + } + + /* Start recording */ + if (waveInStart(self->hwi) != MMSYSERR_NOERROR) { + av_log(s1, AV_LOG_ERROR, "Cannot start recording.\n"); + + wavein_read_close(s1); + return AVERROR(EIO); + } + + return 0; +} + + +/** + * Read one packet and put it in 'pkt'. + * + * @param s1 Context from avformat core + * @param pkt Packet + * @return 0 if success + */ +static int +wavein_read_packet(AVFormatContext *s1, AVPacket *pkt) +{ + struct wavein_data *self = WAVEIN_CAST(s1->priv_data); + AVPacketList *hpktl = NULL; + + /* Wait until WAVEHDR buffer has been filled */ + while (!hpktl) { + WaitForSingleObject(self->wavein_mutex, INFINITE); + + hpktl = self->pktl; + if (hpktl) { + *pkt = self->pktl->pkt; + self->pktl = self->pktl->next; + av_free(hpktl); + } + + ResetEvent(self->wavein_event); + ReleaseMutex(self->wavein_mutex); + + if (hpktl == NULL) { + if (s1->flags & AVFMT_FLAG_NONBLOCK) { + return AVERROR(EAGAIN); + } + else { + WaitForSingleObject(self->wavein_event, INFINITE); + } + } + } + + /* Align current buffer size */ + self->bufsize -= pkt->size; + + return 0; +} + + +/** + * Close the stream + * + * @param s1 Context from avformat core + * @return 0 if success + */ +static int +wavein_read_close(AVFormatContext *s1) +{ + wavein_close(s1); + return 0; +} + + +/** + * wavein demuxer declaration + */ +AVInputFormat wavein_demuxer = { + "wavein", + NULL_IF_CONFIG_SMALL("wavein demuxer"), + sizeof(struct wavein_data), + NULL, + wavein_read_header, + wavein_read_packet, + wavein_read_close, + NULL, + NULL, + AVFMT_NOFILE, +};