Ticket #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
comment:2 Changed 6 months ago by DonMoir
- Keywords win dshow added
- Component changed from undetermined to avdevice
comment:3 Changed 6 months ago by DonMoir
fix type-o in dshow_read_header
- int ret = AVERROR(EIO); // fix return value + ret = AVERROR(EIO); // fix return value
comment:4 Changed 5 months ago by cehoyos
- Status changed from new to closed
- Resolution set to fixed
Ramiro applied a fix that was based on this ticket.



In dshow_read_packet: