From 56f529915f606845d57811e5f97e93c9de55346f Mon Sep 17 00:00:00 2001
From: rogerdpack <rogerpack2005@gmail.com>
Date: Wed, 5 Apr 2023 14:51:48 -0600
Subject: [PATCH 2/2] dshow: add ability to capture closed caption EIA 608 byte
 pairs from e.g. VHS tape capture devices

---
 doc/indevs.texi             |  15 ++-
 libavdevice/dshow.c         | 239 +++++++++++++++++++++++-------------
 libavdevice/dshow_capture.h |  17 +--
 libavdevice/dshow_pin.c     |  14 ++-
 4 files changed, 189 insertions(+), 96 deletions(-)

diff --git a/doc/indevs.texi b/doc/indevs.texi
index 863536a34d..d236b9235b 100644
--- a/doc/indevs.texi
+++ b/doc/indevs.texi
@@ -492,8 +492,12 @@ The input name should be in the format:
 @var{TYPE}=@var{NAME}[:@var{TYPE}=@var{NAME}]
 @end example
 
-where @var{TYPE} can be either @var{audio} or @var{video},
-and @var{NAME} is the device's name or alternative name..
+where @var{TYPE} can be either @var{audio}, @var{video}, or @var{closed_caption}
+and @var{NAME} is the device's name or alternative name.
+
+@var{closed_caption} devices must advertise format VBI and have an intermediate
+directshow filter available to convert from VBI to raw EIA 608 closed caption
+format byte pairs.
 
 @subsection Options
 
@@ -661,6 +665,13 @@ Open video device @var{Camera} and audio device @var{Microphone}:
 $ ffmpeg -f dshow -i video="Camera":audio="Microphone"
 @end example
 
+@item
+Open video device @var{Camera}, closed caption device @var{Camera},
+and audio device @var{Microphone}:
+@example
+$ ffmpeg -f dshow -i video="Camera":audio="Microphone":closed_caption="Video Capture"
+@end example
+
 @item
 Print the list of supported options in selected device and exit:
 @example
diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c
index 5946a72cc2..95b5b28dff 100644
--- a/libavdevice/dshow.c
+++ b/libavdevice/dshow.c
@@ -270,24 +270,35 @@ dshow_read_close(AVFormatContext *s)
         ff_dshow_pin_Release(ctx->capture_pin[VideoDevice]);
     if (ctx->capture_pin[AudioDevice])
         ff_dshow_pin_Release(ctx->capture_pin[AudioDevice]);
+    if (ctx->capture_pin[ClosedCaptionDevice])
+        ff_dshow_pin_Release(ctx->capture_pin[ClosedCaptionDevice]);
     if (ctx->capture_filter[VideoDevice])
         ff_dshow_filter_Release(ctx->capture_filter[VideoDevice]);
     if (ctx->capture_filter[AudioDevice])
         ff_dshow_filter_Release(ctx->capture_filter[AudioDevice]);
+    if (ctx->capture_filter[ClosedCaptionDevice])
+        ff_dshow_filter_Release(ctx->capture_filter[ClosedCaptionDevice]);
 
     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])
+    if (ctx->device_pin[ClosedCaptionDevice])
+        IPin_Release(ctx->device_pin[ClosedCaptionDevice]);
+
+      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_filter[ClosedCaptionDevice])
+        IBaseFilter_Release(ctx->device_filter[ClosedCaptionDevice]);
 
     av_freep(&ctx->device_name[0]);
     av_freep(&ctx->device_name[1]);
+    av_freep(&ctx->device_name[2]);
     av_freep(&ctx->device_unique_name[0]);
     av_freep(&ctx->device_unique_name[1]);
+    av_freep(&ctx->device_unique_name[2]);
 
     if(ctx->mutex)
         CloseHandle(ctx->mutex);
@@ -325,18 +336,22 @@ static int shall_we_drop(AVFormatContext *s, int index, enum dshowDeviceType dev
     static const uint8_t dropscore[] = {62, 75, 87, 100};
     const int ndropscores = FF_ARRAY_ELEMS(dropscore);
     unsigned int buffer_fullness = (ctx->curbufsize[index]*100)/s->max_picture_buffer;
-    const char *devtypename = (devtype == VideoDevice) ? "video" : "audio";
+    const char *devtypename = (devtype == VideoDevice) ? "video" : (devtype == AudioDevice) ? "audio" : "VBI";
 
     if(dropscore[++ctx->video_frame_num%ndropscores] <= buffer_fullness) {
         av_log(s, AV_LOG_ERROR,
               "real-time buffer [%s] [%s input] too full or near too full (%d%% of size: %d [rtbufsize parameter])! frame dropped!\n",
               ctx->device_name[devtype], devtypename, buffer_fullness, s->max_picture_buffer);
         return 1;
+    } else {
+      av_log(s, AV_LOG_DEBUG, "received packet from capture device [%s] [%s input] buffer current size: %lld, queueing\n",
+              ctx->device_name[devtype], devtypename, ctx->curbufsize[index]);
     }
 
     return 0;
 }
 
+// pins call this when they receive data from a capture device
 static void
 callback(void *priv_data, int index, uint8_t *buf, int buf_size, int64_t time, enum dshowDeviceType devtype)
 {
@@ -384,7 +399,7 @@ dshow_get_device_media_types(AVFormatContext *avctx, enum dshowDeviceType devtyp
 {
     IEnumPins *pins = 0;
     IPin *pin;
-    int has_audio = 0, has_video = 0;
+    int has_audio = 0, has_video = 0, has_vbi = 0;
 
     if (IBaseFilter_EnumPins(device_filter, &pins) != S_OK)
         return;
@@ -422,6 +437,8 @@ dshow_get_device_media_types(AVFormatContext *avctx, enum dshowDeviceType devtyp
                 has_video = 1;
             } else if (IsEqualGUID(&type->majortype, &MEDIATYPE_Audio)) {
                 has_audio = 1;
+            } else if (IsEqualGUID(&type->majortype, &MEDIATYPE_VBI)) {
+                has_vbi = 1;
             }
             CoTaskMemFree(type);
         }
@@ -437,14 +454,16 @@ dshow_get_device_media_types(AVFormatContext *avctx, enum dshowDeviceType devtyp
 
     IEnumPins_Release(pins);
 
-    if (has_audio || has_video) {
-        int nb_types = has_audio + has_video;
+    if (has_audio || has_video || has_vbi) {
+        int nb_types = has_audio + has_video + has_vbi;
         *media_types = av_malloc_array(nb_types, sizeof(enum AVMediaType));
         if (*media_types) {
             if (has_audio)
                 (*media_types)[0] = AVMEDIA_TYPE_AUDIO;
             if (has_video)
                 (*media_types)[0 + has_audio] = AVMEDIA_TYPE_VIDEO;
+            if (has_vbi)
+                (*media_types)[0 + has_audio + has_video] = AVMEDIA_TYPE_SUBTITLE;
             *nb_media_types = nb_types;
         }
     }
@@ -474,8 +493,8 @@ dshow_cycle_devices(AVFormatContext *avctx, ICreateDevEnum *devenum,
     int r;
 
     const GUID *device_guid[2] = { &CLSID_VideoInputDeviceCategory,
-                                   &CLSID_AudioInputDeviceCategory };
-    const char *devtypename = (devtype == VideoDevice) ? "video" : "audio only";
+                                   &CLSID_AudioInputDeviceCategory }; // VBI comes off video devices
+    const char *devtypename = (devtype == VideoDevice) ? "video" : (devtype == AudioDevice) ? "audio" : "VBI";
     const char *sourcetypename = (sourcetype == VideoSourceDevice) ? "video" : "audio";
 
     r = ICreateDevEnum_CreateClassEnumerator(devenum, device_guid[sourcetype],
@@ -661,6 +680,9 @@ static int dshow_should_set_format(AVFormatContext *avctx, enum dshowDeviceType
 {
     struct dshow_ctx *ctx = avctx->priv_data;
 
+    if (devtype == ClosedCaptionDevice)
+        return 0; // always use default
+
     return (devtype == VideoDevice && (ctx->framerate ||
                                       (ctx->requested_width && ctx->requested_height) ||
                                        ctx->pixel_format != AV_PIX_FMT_NONE ||
@@ -716,6 +738,10 @@ static struct dshow_format_info *dshow_get_format_info(AM_MEDIA_TYPE *type)
     } else if (IsEqualGUID(&type->formattype, &FORMAT_WaveFormatEx)) {
         fx = (void *) type->pbFormat;
         devtype = AudioDevice;
+    } else if (IsEqualGUID(&type->majortype, &MEDIATYPE_VBI) || IsEqualGUID(&type->majortype, &MEDIATYPE_AUXLine21Data) && IsEqualGUID(&type->subtype, &MEDIASUBTYPE_Line21_BytePair )) {
+        // MEDIATYPE_VBI is when we're first enumerating, MEDIATYPE_AUXLine21Data is what the pin gets
+        // set to in the end, and in dshow_add_device we lookup using that now, apparently...
+        devtype = ClosedCaptionDevice;
     } else {
         return NULL;
     }
@@ -749,11 +775,11 @@ static struct dshow_format_info *dshow_get_format_info(AM_MEDIA_TYPE *type)
             fmt_info->col_trc = dshow_color_trc(extended_format_info);
             fmt_info->chroma_loc = dshow_chroma_loc(extended_format_info);
         }
-    } else {
+    } else if (devtype == AudioDevice) {
         fmt_info->sample_rate = fx->nSamplesPerSec;
         fmt_info->sample_size = fx->wBitsPerSample;
         fmt_info->channels = fx->nChannels;
-    }
+    } // else VBI do nothing
 
     return fmt_info;
 }
@@ -761,25 +787,29 @@ static struct dshow_format_info *dshow_get_format_info(AM_MEDIA_TYPE *type)
 static void dshow_get_default_format(IPin *pin, IAMStreamConfig *config, enum dshowDeviceType devtype, AM_MEDIA_TYPE **type)
 {
     HRESULT hr;
-
-    if ((hr = IAMStreamConfig_GetFormat(config, type)) != S_OK) {
-        if (hr == E_NOTIMPL || !IsEqualGUID(&(*type)->majortype, devtype == VideoDevice ? &MEDIATYPE_Video : &MEDIATYPE_Audio)) {
-            // default not available or of wrong type,
-            // fall back to iterating exposed formats
-            // until one of the right type is found
-            IEnumMediaTypes* types = NULL;
-            if (IPin_EnumMediaTypes(pin, &types) != S_OK)
-                return;
-            IEnumMediaTypes_Reset(types);
-            while (IEnumMediaTypes_Next(types, 1, type, NULL) == S_OK) {
-                if (IsEqualGUID(&(*type)->majortype, devtype == VideoDevice ? &MEDIATYPE_Video : &MEDIATYPE_Audio)) {
-                    break;
-                }
-                CoTaskMemFree(*type);
-                *type = NULL;
+    GUID expected_media_type = devtype == VideoDevice ? MEDIATYPE_Video : devtype == AudioDevice ? MEDIATYPE_Audio : MEDIATYPE_VBI;
+    // GetFormat, since we haven't called SetFormat yet, should return us the "preferred type"
+    hr = IAMStreamConfig_GetFormat(config, type);
+    if (hr != S_OK || !IsEqualGUID(&(*type)->majortype, &expected_media_type)) {
+        // default not available or of wrong type,
+        // fall back to iterating exposed formats
+        // until one of the right type is found
+        IEnumMediaTypes* types = NULL;
+        if (*type) {
+            CoTaskMemFree(*type);
+            *type = NULL;
+        }
+        if (IPin_EnumMediaTypes(pin, &types) != S_OK)
+            return;
+        IEnumMediaTypes_Reset(types);
+        while (IEnumMediaTypes_Next(types, 1, type, NULL) == S_OK) {
+            if (IsEqualGUID(&(*type)->majortype, &expected_media_type)) {
+                break; // success
             }
-            IEnumMediaTypes_Release(types);
+            CoTaskMemFree(*type);
+            *type = NULL;
         }
+        IEnumMediaTypes_Release(types);
     }
 }
 
@@ -838,39 +868,38 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
     use_default = !dshow_should_set_format(avctx, devtype);
     if (use_default && pformat_set)
     {
-        // get default
+        struct dshow_format_info *fmt_info;
+        // lookup default
         dshow_get_default_format(pin, config, devtype, &type);
         if (!type)
             // this pin does not expose any formats of the expected type
             goto end;
 
-        if (type) {
-            // interrogate default format, so we know what to search for below
-            struct dshow_format_info *fmt_info = dshow_get_format_info(type);
-            if (fmt_info) {
-                if (fmt_info->devtype == VideoDevice) {
-                    requested_video_codec_id = fmt_info->codec_id;
-                    requested_pixel_format   = fmt_info->pix_fmt;
-                    requested_framerate      = fmt_info->framerate;
-                    requested_width          = fmt_info->width;
-                    requested_height         = fmt_info->height;
-                } else {
-                    requested_sample_rate = fmt_info->sample_rate;
-                    requested_sample_size = fmt_info->sample_size;
-                    requested_channels    = fmt_info->channels;
-                }
-                av_free(fmt_info);  // free but don't set to NULL to enable below check
-            }
+        // interrogate default format, so we know what to search for below
+        fmt_info = dshow_get_format_info(type);
+        if (fmt_info) {
+            if (fmt_info->devtype == VideoDevice) {
+                requested_video_codec_id = fmt_info->codec_id;
+                requested_pixel_format   = fmt_info->pix_fmt;
+                requested_framerate      = fmt_info->framerate;
+                requested_width          = fmt_info->width;
+                requested_height         = fmt_info->height;
+            } else if (fmt_info->devtype == AudioDevice) {
+                requested_sample_rate = fmt_info->sample_rate;
+                requested_sample_size = fmt_info->sample_size;
+                requested_channels    = fmt_info->channels;
+            } // VBI just is its own default
+            av_free(fmt_info);  // free but don't set to NULL to enable below check
+        } // else some unknown format
 
-            if (type && type->pbFormat)
-                CoTaskMemFree(type->pbFormat);
-            CoTaskMemFree(type);
-            type = NULL;
-            if (!fmt_info)
-                // default format somehow invalid, can't continue with this pin
-                goto end;
-            fmt_info = NULL;
-        }
+        if (type->pbFormat)
+            CoTaskMemFree(type->pbFormat);
+        CoTaskMemFree(type);
+        type = NULL;
+        if (!fmt_info)
+            // default format somehow invalid, can't continue with this pin
+            goto end;
+        fmt_info = NULL;
     }
 
     // NB: some devices (e.g. Logitech C920) expose each video format twice:
@@ -890,7 +919,6 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
 #if DSHOWDEBUG
         ff_print_AM_MEDIA_TYPE(type);
 #endif
-
         fmt_info = dshow_get_format_info(type);
         if (!fmt_info)
             goto next;
@@ -982,7 +1010,7 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
                 bih->biWidth  = requested_width;
                 bih->biHeight = requested_height;
             }
-        } else {
+        } else if (devtype == AudioDevice) {
             WAVEFORMATEX *fx;
             AUDIO_STREAM_CONFIG_CAPS *acaps = caps;
 #if DSHOWDEBUG
@@ -1009,7 +1037,15 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
             ) {
                 goto next;
             }
-        }
+        } else {
+            // VBI
+	    if (IsEqualGUID(&type->majortype, &MEDIATYPE_VBI)) {
+                // success
+            } else {
+                av_log(avctx, AV_LOG_INFO, "Got unknown VBI type? Ignoring..."); // shouldn't happen
+                goto next;
+	    }
+	}
 
         // found a matching format. Either apply or store
         // for safekeeping if we might maybe find a better
@@ -1018,7 +1054,7 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
         if (!wait_for_better) {
             if (IAMStreamConfig_SetFormat(config, type) != S_OK)
                 goto next;
-            format_set = 1;
+            format_set = 1; // this will break out of the loop now
         }
         else if (!previous_match_type) {
             // store this matching format for possible later use.
@@ -1028,7 +1064,7 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
         }
 next:
         av_freep(&fmt_info);
-        if (type && type->pbFormat)
+        if (type->pbFormat)
             CoTaskMemFree(type->pbFormat);
         CoTaskMemFree(type);
         type = NULL;
@@ -1051,12 +1087,14 @@ next:
             // in the capabilities of any of the formats returned by the device
             // (sic?). Fall back to directly setting the default format
             dshow_get_default_format(pin, config, devtype, &type);
-            if (IAMStreamConfig_SetFormat(config, type) == S_OK)
-                format_set = 1;
-            if (type && type->pbFormat)
-                CoTaskMemFree(type->pbFormat);
-            CoTaskMemFree(type);
-            type = NULL;
+            if (type) {
+	        if (IAMStreamConfig_SetFormat(config, type) == S_OK)
+                    format_set = 1;
+                if (type->pbFormat)
+                    CoTaskMemFree(type->pbFormat);
+                CoTaskMemFree(type);
+                type = NULL;
+	    }
         }
     }
 
@@ -1178,8 +1216,9 @@ dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
     IPin *pin;
     int r;
 
-    const char *devtypename = (devtype == VideoDevice) ? "video" : "audio only";
-    const char *sourcetypename = (sourcetype == VideoSourceDevice) ? "video" : "audio";
+    // we look for audio on video filter source as a fallback on occasion, and VBI on video always...
+    const char *devtypename = (devtype == VideoDevice) ? "video" : (devtype == AudioDevice) ? "audio" : "VBI";
+    const char *sourcetypename = (sourcetype == VideoSourceDevice) ? "video" : "audio only";
 
     int set_format = dshow_should_set_format(avctx, devtype);
     int format_set = 0;
@@ -1219,7 +1258,7 @@ dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
         if (IKsPropertySet_Get(p, &AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY,
                                NULL, 0, &category, sizeof(GUID), &r2) != S_OK)
             goto next;
-        if (!IsEqualGUID(&category, &PIN_CATEGORY_CAPTURE))
+        if (!IsEqualGUID(&category, &PIN_CATEGORY_CAPTURE) && !IsEqualGUID(&category, &PIN_CATEGORY_VBI))
             goto next;
         name_buf = dup_wchar_to_utf8(info.achName);
 
@@ -1330,10 +1369,9 @@ dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum,
     IStream *ifile_stream = NULL;
     IStream *ofile_stream = NULL;
     IPersistStream *pers_stream = NULL;
-    enum dshowDeviceType otherDevType = (devtype == VideoDevice) ? AudioDevice : VideoDevice;
-
-    const wchar_t *filter_name[2] = { L"Audio capture filter", L"Video capture filter" };
+    enum dshowDeviceType all_device_types[3] = {VideoDevice, AudioDevice, ClosedCaptionDevice};
 
+    const wchar_t *filter_name[3] = { L"Audio ffmpeg capture filter", L"Video ffmpeg capture filter", L"VBI ffmpeg capture filter" };
 
     if ( ((ctx->audio_filter_load_file) && (strlen(ctx->audio_filter_load_file)>0) && (sourcetype == AudioSourceDevice)) ||
             ((ctx->video_filter_load_file) && (strlen(ctx->video_filter_load_file)>0) && (sourcetype == VideoSourceDevice)) ) {
@@ -1369,16 +1407,23 @@ dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum,
             goto error;
         }
     }
-        if (ctx->device_filter[otherDevType]) {
-        // avoid adding add two instances of the same device to the graph, one for video, one for audio
-        // a few devices don't support this (could also do this check earlier to avoid double crossbars, etc. but they seem OK)
-        if (strcmp(device_filter_unique_name, ctx->device_unique_name[otherDevType]) == 0) {
-          av_log(avctx, AV_LOG_DEBUG, "reusing previous graph capture filter... %s\n", device_filter_unique_name);
-          IBaseFilter_Release(device_filter);
-          device_filter = ctx->device_filter[otherDevType];
-          IBaseFilter_AddRef(ctx->device_filter[otherDevType]);
-        } else {
-            av_log(avctx, AV_LOG_DEBUG, "not reusing previous graph capture filter %s != %s\n", device_filter_unique_name, ctx->device_unique_name[otherDevType]);
+    for (int i = 0; i < sizeof(all_device_types) / sizeof(all_device_types[0]); i++) {
+        enum dshowDeviceType candidate = all_device_types[i];
+        if (candidate == devtype) {
+            continue; // ourself hasn't added one yet, skip
+        }
+        if (ctx->device_filter[candidate]) {
+            // avoid adding add two instances of the same device to the graph, one for video, one for audio
+            // a few devices don't support this (could also do this check earlier to avoid double crossbars, etc. but they seem OK)
+            if (strcmp(device_filter_unique_name, ctx->device_unique_name[candidate]) == 0) {
+                av_log(avctx, AV_LOG_DEBUG, "reusing previous graph capture filter... %s\n", device_filter_unique_name);
+                IBaseFilter_Release(device_filter);
+                device_filter = ctx->device_filter[candidate];
+                IBaseFilter_AddRef(ctx->device_filter[candidate]);
+            } else {
+                av_log(avctx, AV_LOG_DEBUG, "not reusing previous graph capture filter %s != %s\n", device_filter_unique_name, ctx->device_unique_name[candidate]);
+            }
+            break;
         }
     }
 
@@ -1548,6 +1593,7 @@ dshow_add_device(AVFormatContext *avctx,
     ff_dshow_pin_ConnectionMediaType(ctx->capture_pin[devtype], &type);
     fmt_info = dshow_get_format_info(&type);
     if (!fmt_info) {
+        av_log(avctx, AV_LOG_ERROR, "Could not parse pin's media type format?\n");
         ret = AVERROR(EIO);
         goto error;
     }
@@ -1611,7 +1657,7 @@ dshow_add_device(AVFormatContext *avctx,
             }
             par->bits_per_coded_sample = bih->biBitCount;
         }
-    } else {
+    } else if (devtype == AudioDevice) {
         if (!IsEqualGUID(&type.formattype, &FORMAT_WaveFormatEx)) {
             av_log(avctx, AV_LOG_ERROR, "Could not get media type.\n");
             goto error;
@@ -1622,6 +1668,10 @@ dshow_add_device(AVFormatContext *avctx,
         par->codec_id    = waveform_codec_id(par->format);
         par->sample_rate = fmt_info->sample_rate;
         par->ch_layout.nb_channels = fmt_info->channels;
+    } else {
+        // vbi
+        par->codec_type  = AVMEDIA_TYPE_SUBTITLE;
+        par->codec_id    = AV_CODEC_ID_EIA_608_BYTE_PAIRS;
     }
 
     avpriv_set_pts_info(st, 64, 1, 10000000);
@@ -1648,24 +1698,29 @@ static int parse_device_name(AVFormatContext *avctx)
         char *token = strtok(NULL, ":");
         tmp = NULL;
 
-        if        (!strcmp(type, "video")) {
+        if (!strcmp(type, "video")) {
             device_name[0] = token;
         } else if (!strcmp(type, "audio")) {
             device_name[1] = token;
+        } else if (!strcmp(type, "closed_caption")) {
+            device_name[2] = token;
         } else {
             device_name[0] = NULL;
             device_name[1] = NULL;
+            device_name[2] = NULL;
             break;
         }
     }
 
-    if (!device_name[0] && !device_name[1]) {
+    if (!device_name[0] && !device_name[1] && !device_name[2]) {
         ret = 0;
     } else {
         if (device_name[0])
             device_name[0] = av_strdup(device_name[0]);
         if (device_name[1])
             device_name[1] = av_strdup(device_name[1]);
+        if (device_name[2])
+            device_name[2] = av_strdup(device_name[2]);
     }
 
     av_free(name);
@@ -1745,6 +1800,12 @@ static int dshow_read_header(AVFormatContext *avctx)
                 }
             }
         }
+        if (ctx->device_name[ClosedCaptionDevice]) {
+            if ((r = dshow_list_device_options(avctx, devenum, ClosedCaptionDevice, VideoSourceDevice))) {
+                ret = r;
+                goto error;
+            }
+	}
         // don't exit yet, allow it to list crossbar options in dshow_open_device
     }
     if (ctx->device_name[VideoDevice]) {
@@ -1761,11 +1822,19 @@ static int dshow_read_header(AVFormatContext *avctx)
             /* see if there's a video source with an audio pin with the given audio name */
             if ((r = dshow_open_device(avctx, devenum, AudioDevice, VideoSourceDevice)) < 0 ||
                 (r = dshow_add_device(avctx, AudioDevice)) < 0) {
+                av_log(avctx, AV_LOG_INFO, "fail audio device within video devices for %s\n", ctx->device_name[AudioDevice]);
                 ret = r;
                 goto error;
             }
         }
     }
+    if (ctx->device_name[ClosedCaptionDevice]) {
+        if ((r = dshow_open_device(avctx, devenum, ClosedCaptionDevice, VideoSourceDevice)) < 0 ||
+            (r = dshow_add_device(avctx, ClosedCaptionDevice)) < 0) {
+            ret = r;
+            goto error;
+        }
+    }
     if (ctx->list_options) {
         /* allow it to list crossbar options in dshow_open_device */
         ret = AVERROR_EXIT;
@@ -1880,7 +1949,9 @@ static int dshow_read_packet(AVFormatContext *s, AVPacket *pkt)
             }
         }
     }
-
+    if (pkt) {
+      av_log(ctx, AV_LOG_DEBUG, "passing to pipeline packet of size %8d\n", pkt->size);
+    }
     return ctx->eof ? AVERROR(EIO) : pkt->size;
 }
 
diff --git a/libavdevice/dshow_capture.h b/libavdevice/dshow_capture.h
index b548cd7afc..494f70c784 100644
--- a/libavdevice/dshow_capture.h
+++ b/libavdevice/dshow_capture.h
@@ -62,8 +62,10 @@ struct GUIDoffset {
 enum dshowDeviceType {
     VideoDevice = 0,
     AudioDevice = 1,
+    ClosedCaptionDevice = 2,
 };
 
+// We sometimes want "something besides video from a video source device" etc. but differentiate this way:
 enum dshowSourceFilterType {
     VideoSourceDevice = 0,
     AudioSourceDevice = 1,
@@ -289,8 +291,8 @@ struct dshow_ctx {
 
     IGraphBuilder *graph;
 
-    char *device_name[2];
-    char *device_unique_name[2];
+    char *device_name[3];
+    char *device_unique_name[3];
 
     int video_device_number;
     int audio_device_number;
@@ -314,10 +316,10 @@ struct dshow_ctx {
     char *video_filter_save_file;
     int   use_video_device_timestamps;
 
-    IBaseFilter *device_filter[2];
-    IPin        *device_pin[2];
-    DShowFilter *capture_filter[2];
-    DShowPin    *capture_pin[2];
+    IBaseFilter *device_filter[3];
+    IPin        *device_pin[3];
+    DShowFilter *capture_filter[3];
+    DShowPin    *capture_pin[3];
 
     HANDLE mutex;
     HANDLE event[2]; /* event[0] is set by DirectShow
@@ -326,7 +328,7 @@ struct dshow_ctx {
 
     int eof;
 
-    int64_t curbufsize[2];
+    int64_t curbufsize[3];
     unsigned int video_frame_num;
 
     IMediaControl *control;
@@ -353,4 +355,5 @@ HRESULT ff_dshow_try_setup_crossbar_options(ICaptureGraphBuilder2 *graph_builder
 
 void ff_dshow_show_filter_properties(IBaseFilter *pFilter, AVFormatContext *avctx);
 
+
 #endif /* AVDEVICE_DSHOW_CAPTURE_H */
diff --git a/libavdevice/dshow_pin.c b/libavdevice/dshow_pin.c
index f846b68918..f6c8f05482 100644
--- a/libavdevice/dshow_pin.c
+++ b/libavdevice/dshow_pin.c
@@ -50,9 +50,17 @@ long WINAPI ff_dshow_pin_ReceiveConnection(DShowPin *this, IPin *pin,
     if (devtype == VideoDevice) {
         if (!IsEqualGUID(&type->majortype, &MEDIATYPE_Video))
             return VFW_E_TYPE_NOT_ACCEPTED;
-    } else {
+    } else if (devtype == AudioDevice) {
         if (!IsEqualGUID(&type->majortype, &MEDIATYPE_Audio))
             return VFW_E_TYPE_NOT_ACCEPTED;
+    } else {
+        // VBI: only accept "Line21 byte pairs" [which forces an intermediary filter to convert from
+        // FORMAT_VBI to byte pairs which are easier to parse by the decoder]
+        if (IsEqualGUID(&type->majortype, &MEDIATYPE_AUXLine21Data) && IsEqualGUID(&type->subtype, &MEDIASUBTYPE_Line21_BytePair )) {
+            // success
+	} else {
+            return VFW_E_TYPE_NOT_ACCEPTED;
+	}
     }
 
     IPin_AddRef(pin);
@@ -299,7 +307,7 @@ long WINAPI ff_dshow_meminputpin_Receive(DShowMemInputPin *this, IMediaSample *s
     int64_t sampletime = 0;
     int64_t graphtime = 0;
     int use_sample_time = 1;
-    const char *devtypename = (devtype == VideoDevice) ? "video" : "audio";
+    const char *devtypename = (devtype == VideoDevice) ? "video" : (devtype == AudioDevice) ? "audio" : "VBI";
     IReferenceClock *clock = pin->filter->clock;
     int64_t dummy;
     struct dshow_ctx *ctx;
@@ -347,7 +355,7 @@ long WINAPI ff_dshow_meminputpin_Receive(DShowMemInputPin *this, IMediaSample *s
     IMediaSample_GetPointer(sample, &buf);
     index = pin->filter->stream_index;
 
-    av_log(s, AV_LOG_VERBOSE, "passing through packet of type %s size %8d "
+    av_log(s, AV_LOG_VERBOSE, "captured packet of type %s size %8d "
         "timestamp %"PRId64" orig timestamp %"PRId64" graph timestamp %"PRId64" diff %"PRId64" %s\n",
         devtypename, buf_size, chosentime, sampletime, graphtime, graphtime - sampletime, ctx->device_name[devtype]);
     pin->filter->callback(priv_data, index, buf, buf_size, chosentime, devtype);
-- 
2.17.1

