#1977 closed defect (fixed)
dshow hang when device is unplugged
Reported by: | DonMoir | Owned by: | |
---|---|---|---|
Priority: | normal | Component: | avdevice |
Version: | unspecified | Keywords: | win dshow |
Cc: | Blocked By: | ||
Blocking: | Reproduced by developer: | no | |
Analyzed by developer: | no |
Description
dshow.c doesn't do any event handling so when you unplug a currently opened device, it will hang in dshow_read_packet since the ctx->event will never be signaled.
See WaitForSingleObject (ctx->event,INFINITE) in dshow_read_packet.
Since there are not going to be anymore packets after the device is unplugged then ctx->event will not be signaled and it will wait forever.
All changes are in dshow.c I tested the following with 2 different cameras and still testing more.
struct dshow_ctx needs to change:
struct dshow_ctx { const AVClass *class; IGraphBuilder *graph; char *device_name[2]; int video_device_number; int audio_device_number; int list_options; int list_devices; int audio_buffer_size; IBaseFilter *device_filter[2]; IPin *device_pin[2]; libAVFilter *capture_filter[2]; libAVPin *capture_pin[2]; HANDLE mutex; HANDLE event; AVPacketList *pktl; int64_t curbufsize; unsigned int video_frame_num; IMediaControl *control; + IMediaEvent *media_event; + int eof; // signal to stop trying to read packets enum AVPixelFormat pixel_format; enum AVCodecID video_codec_id; char *framerate; int requested_width; int requested_height; AVRational requested_framerate; int sample_rate; int sample_size; int channels; };
dshow_read_header changes. Adds code to retrieve IMediaEvent and fixes return value on open failure. (see ticket #1976)
static int dshow_read_header(AVFormatContext *avctx) { struct dshow_ctx *ctx = avctx->priv_data; IGraphBuilder *graph = NULL; ICreateDevEnum *devenum = NULL; IMediaControl *control = NULL; + IMediaEvent *media_event = NULL; int ret = AVERROR(EIO); int r; if (!ctx->list_devices && !parse_device_name(avctx)) { av_log(avctx, AV_LOG_ERROR, "Malformed dshow input string.\n"); goto error; } ctx->video_codec_id = avctx->video_codec_id ? avctx->video_codec_id : AV_CODEC_ID_RAWVIDEO; if (ctx->pixel_format != AV_PIX_FMT_NONE) { if (ctx->video_codec_id != AV_CODEC_ID_RAWVIDEO) { av_log(avctx, AV_LOG_ERROR, "Pixel format may only be set when " "video codec is not set or set to rawvideo\n"); ret = AVERROR(EINVAL); goto error; } } if (ctx->framerate) { r = av_parse_video_rate(&ctx->requested_framerate, ctx->framerate); if (r < 0) { av_log(avctx, AV_LOG_ERROR, "Could not parse framerate '%s'.\n", ctx->framerate); goto error; } } CoInitialize(0); r = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder, (void **) &graph); if (r != S_OK) { av_log(avctx, AV_LOG_ERROR, "Could not create capture graph.\n"); goto error; } ctx->graph = graph; r = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, &IID_ICreateDevEnum, (void **) &devenum); if (r != S_OK) { av_log(avctx, AV_LOG_ERROR, "Could not enumerate system devices.\n"); goto error; } if (ctx->list_devices) { av_log(avctx, AV_LOG_INFO, "DirectShow video devices\n"); dshow_cycle_devices(avctx, devenum, VideoDevice, NULL); av_log(avctx, AV_LOG_INFO, "DirectShow audio devices\n"); dshow_cycle_devices(avctx, devenum, AudioDevice, NULL); ret = AVERROR_EXIT; goto error; } if (ctx->list_options) { if (ctx->device_name[VideoDevice]) dshow_list_device_options(avctx, devenum, VideoDevice); if (ctx->device_name[AudioDevice]) dshow_list_device_options(avctx, devenum, AudioDevice); ret = AVERROR_EXIT; goto error; } if (ctx->device_name[VideoDevice]) { ret = dshow_open_device(avctx, devenum, VideoDevice); if (ret < 0) goto error; ret = dshow_add_device(avctx, VideoDevice); if (ret < 0) goto error; } if (ctx->device_name[AudioDevice]) { ret = dshow_open_device(avctx, devenum, AudioDevice); if (ret < 0) goto error; ret = dshow_add_device(avctx, AudioDevice); if (ret < 0) goto error; } + int ret = AVERROR(EIO); // fix return value ctx->mutex = CreateMutex(NULL, 0, NULL); if (!ctx->mutex) { av_log(avctx, AV_LOG_ERROR, "Could not create Mutex\n"); goto error; } ctx->event = CreateEvent(NULL, 1, 0, NULL); if (!ctx->event) { av_log(avctx, AV_LOG_ERROR, "Could not create Event\n"); goto error; } r = IGraphBuilder_QueryInterface(graph, &IID_IMediaControl, (void **) &control); if (r != S_OK) { av_log(avctx, AV_LOG_ERROR, "Could not get media control.\n"); goto error; } ctx->control = control; + r = IGraphBuilder_QueryInterface(graph, &IID_IMediaEvent, (void **) &media_event); + if (r != S_OK) { + av_log(avctx, AV_LOG_ERROR, "Could not get media event.\n"); + goto error; + } + ctx->media_event = media_event; r = IMediaControl_Run(control); if (r == S_FALSE) { OAFilterState pfs; r = IMediaControl_GetState(control, 0, &pfs); } if (r != S_OK) { av_log(avctx, AV_LOG_ERROR, "Could not run filter\n"); goto error; } ret = 0; error: if (ret < 0) dshow_read_close(avctx); if (devenum) ICreateDevEnum_Release(devenum); return ret; }
Adds check_event_code to check for device lost or other failures.
// note: EC_DEVICE_LOST is not defined in MinGW dshow headers. +#ifndef EC_DEVICE_LOST +#define EC_DEVICE_LOST 0x1f +#endif + +static void check_event_code (struct dshow_ctx *ctx) +{ + long event_code, param1, param2; + while (IMediaEvent_GetEvent (ctx->media_event,&event_code,¶m1,¶m2,0) + != E_ABORT) + { + if (event_code == EC_COMPLETE || event_code == EC_DEVICE_LOST || + event_code == EC_ERRORABORT) + ctx->eof = 1; + IMediaEvent_FreeEventParams (ctx->media_event,event_code,param1,param2); + } +}
Changes dshow_read_packet to call check_event_code and to check ctx->eof
static int dshow_read_packet(AVFormatContext *s, AVPacket *pkt) { struct dshow_ctx *ctx = s->priv_data; AVPacketList *pktl = NULL; - while (!pktl) { + while (!ctx->eof && !pktl) { WaitForSingleObject(ctx->mutex, INFINITE); pktl = ctx->pktl; if (pktl) { *pkt = pktl->pkt; ctx->pktl = ctx->pktl->next; av_free(pktl); ctx->curbufsize -= pkt->size; } ResetEvent(ctx->event); ReleaseMutex(ctx->mutex); if (!pktl) { - if (s->flags & AVFMT_FLAG_NONBLOCK) { - return AVERROR(EAGAIN); - } else { - WaitForSingleObject(ctx->event, INFINITE); - } + if (s->flags & AVFMT_FLAG_NONBLOCK) + return AVERROR(EAGAIN); + else if (WaitForSingleObject(ctx->event, 200) == WAIT_TIMEOUT) + check_event_code (ctx); } } return ctx->eof ? -1 : pkt->size; }
add check for ctx->eof in callback function as a sanity check
static void callback(void *priv_data, int index, uint8_t *buf, int buf_size, int64_t time) { AVFormatContext *s = (AVFormatContext *)priv_data; struct dshow_ctx *ctx = (struct dshow_ctx *)s->priv_data; AVPacketList **ppktl, *pktl_next; WaitForSingleObject(ctx->mutex, INFINITE); - if(shall_we_drop(s)) + if(ctx->eof || shall_we_drop(s)) goto fail; pktl_next = (AVPacketList *)av_mallocz(sizeof(AVPacketList)); if(!pktl_next) goto fail; if(av_new_packet(&pktl_next->pkt, buf_size) < 0) { av_free(pktl_next); goto fail; } pktl_next->pkt.stream_index = index; pktl_next->pkt.pts = time; memcpy(pktl_next->pkt.data, buf, buf_size); for(ppktl = &ctx->pktl ; *ppktl ; ppktl = &(*ppktl)->next); *ppktl = pktl_next; ctx->curbufsize += buf_size; SetEvent(ctx->event); ReleaseMutex(ctx->mutex); return; fail: ReleaseMutex(ctx->mutex); return; }
release media_event in dshow_read_close and set ctx->eof for sanity.
static int dshow_read_close(AVFormatContext *s) { struct dshow_ctx *ctx = s->priv_data; AVPacketList *pktl; + ctx->eof = 1; if (ctx->control) { IMediaControl_Stop(ctx->control); IMediaControl_Release(ctx->control); } + if (ctx->media_event) + IMediaEvent_Release (ctx->media_event); if (ctx->graph) { IEnumFilters *fenum; int r; r = IGraphBuilder_EnumFilters(ctx->graph, &fenum); if (r == S_OK) { IBaseFilter *f; IEnumFilters_Reset(fenum); while (IEnumFilters_Next(fenum, 1, &f, NULL) == S_OK) { if (IGraphBuilder_RemoveFilter(ctx->graph, f) == S_OK) IEnumFilters_Reset(fenum); /* When a filter is removed, * the list must be reset. */ IBaseFilter_Release(f); } IEnumFilters_Release(fenum); } IGraphBuilder_Release(ctx->graph); } if (ctx->capture_pin[VideoDevice]) libAVPin_Release(ctx->capture_pin[VideoDevice]); if (ctx->capture_pin[AudioDevice]) libAVPin_Release(ctx->capture_pin[AudioDevice]); if (ctx->capture_filter[VideoDevice]) libAVFilter_Release(ctx->capture_filter[VideoDevice]); if (ctx->capture_filter[AudioDevice]) libAVFilter_Release(ctx->capture_filter[AudioDevice]); if (ctx->device_pin[VideoDevice]) IPin_Release(ctx->device_pin[VideoDevice]); if (ctx->device_pin[AudioDevice]) IPin_Release(ctx->device_pin[AudioDevice]); if (ctx->device_filter[VideoDevice]) IBaseFilter_Release(ctx->device_filter[VideoDevice]); if (ctx->device_filter[AudioDevice]) IBaseFilter_Release(ctx->device_filter[AudioDevice]); if (ctx->device_name[0]) av_free(ctx->device_name[0]); if (ctx->device_name[1]) av_free(ctx->device_name[1]); if(ctx->mutex) CloseHandle(ctx->mutex); if(ctx->event) CloseHandle(ctx->event); pktl = ctx->pktl; while (pktl) { AVPacketList *next = pktl->next; av_destruct_packet(&pktl->pkt); av_free(pktl); pktl = next; } return 0; }
Change History (6)
comment:1 by , 12 years ago
comment:2 by , 12 years ago
Component: | undetermined → avdevice |
---|---|
Keywords: | win dshow added |
comment:3 by , 12 years ago
fix type-o in dshow_read_header
- int ret = AVERROR(EIO); // fix return value + ret = AVERROR(EIO); // fix return value
comment:4 by , 12 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
Ramiro applied a fix that was based on this ticket.
comment:5 by , 12 years ago
Thanks,
vfwcap.c may have similar problems but most likely I won't be dealing with that so vfwcap.c will go untested.
In dshow_read_packet: