Opened 4 years ago

Closed 4 years ago

Last modified 4 years ago

#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,&param1,&param2,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 Changed 4 years ago by DonMoir

In dshow_read_packet:

-   return pkt->size;
+   return ctx->eof ? -1 : pkt->size;

comment:2 Changed 4 years ago by DonMoir

  • Component changed from undetermined to avdevice
  • Keywords win dshow added

comment:3 Changed 4 years ago by DonMoir

fix type-o in dshow_read_header

  • int ret = AVERROR(EIO); fix return value

+ ret = AVERROR(EIO); fix return value

Version 0, edited 4 years ago by DonMoir (next)

comment:4 Changed 4 years ago by cehoyos

  • Resolution set to fixed
  • Status changed from new to closed

Ramiro applied a fix that was based on this ticket.

comment:5 Changed 4 years ago by DonMoir

Thanks,

vfwcap.c may have similar problems but most likely I won't be dealing with that so vfwcap.c will go untested.

comment:6 Changed 4 years ago by rogerdpack

I haven't been porting any dshow changes to vfwcap, either...

Note: See TracTickets for help on using tickets.