[FFmpeg-trac] #1977(undetermined:new): dshow hang when device is unplugged

FFmpeg trac at avcodec.org
Thu Nov 29 13:54:10 CET 2012


#1977: dshow hang when device is unplugged
-------------------------------------+-------------------------------------
             Reporter:  DonMoir      |                     Type:  defect
               Status:  new          |                 Priority:  normal
            Component:               |                  Version:
  undetermined                       |  unspecified
             Keywords:               |               Blocked By:
             Blocking:               |  Reproduced by developer:  0
Analyzed by developer:  0            |
-------------------------------------+-------------------------------------
 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;
 }
 }}}

-- 
Ticket URL: <https://ffmpeg.org/trac/ffmpeg/ticket/1977>
FFmpeg <http://ffmpeg.org>
FFmpeg issue tracker


More information about the FFmpeg-trac mailing list