-- v4: winegstreamer: Use an atomic queue for wg_transform input buffers. winegstreamer: Release requested samples if they are too small. winegstreamer: Introduce a new wg_allocator_set_next_sample helper. winegstreamer: Use the GstObject lock instead of a new allocator mutex. mf/tests: Add todo_wine for newer FFmpeg versions.
From: Rémi Bernon rbernon@codeweavers.com
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/mf/tests/mf.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 2f3ee3151da..5f26b1a6238 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -5911,7 +5911,7 @@ static void check_sample_pcm16_(int line, IMFSample *sample, const BYTE *expect_ if (expect - value + 512 > 1024) break; }
- todo_wine_if(todo) + todo_wine_if(todo && i < length / 2) ok_(__FILE__, line)(i == length, "unexpected buffer data\n");
if (output_file) WriteFile(output_file, buffer, length, &length, NULL); @@ -6544,6 +6544,9 @@ static void test_wma_decoder(void) hr = IMFTransform_ProcessOutput(transform, 0, 1, &output, &status);
winetest_pop_context(); + + /* some FFmpeg version request more input to complete decoding */ + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT && i == 2) break; } todo_wine ok(wmadec_data_len == 0, "missing %#lx bytes\n", wmadec_data_len);
From: Rémi Bernon rbernon@codeweavers.com
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/wg_allocator.c | 37 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 20 deletions(-)
diff --git a/dlls/winegstreamer/wg_allocator.c b/dlls/winegstreamer/wg_allocator.c index c31751ce83f..16e961a57d4 100644 --- a/dlls/winegstreamer/wg_allocator.c +++ b/dlls/winegstreamer/wg_allocator.c @@ -57,8 +57,7 @@ typedef struct wg_allocator_request_sample_cb request_sample; void *request_sample_context;
- pthread_mutex_t mutex; - pthread_cond_t release_cond; + GCond release_cond; struct list memory_list; } WgAllocator;
@@ -79,7 +78,7 @@ static gpointer wg_allocator_map(GstMemory *gst_memory, GstMapInfo *info, gsize
GST_LOG("memory %p, info %p, maxsize %#zx", memory, info, maxsize);
- pthread_mutex_lock(&allocator->mutex); + GST_OBJECT_LOCK(allocator);
if (!memory->sample) info->data = memory->unix_map_info.data; @@ -91,7 +90,7 @@ static gpointer wg_allocator_map(GstMemory *gst_memory, GstMapInfo *info, gsize if (info->flags & GST_MAP_WRITE) memory->written = max(memory->written, maxsize);
- pthread_mutex_unlock(&allocator->mutex); + GST_OBJECT_UNLOCK(allocator);
GST_INFO("Mapped memory %p to %p", memory, info->data); return info->data; @@ -107,15 +106,15 @@ static void wg_allocator_unmap(GstMemory *gst_memory, GstMapInfo *info)
GST_LOG("memory %p, info %p", memory, info);
- pthread_mutex_lock(&allocator->mutex); + GST_OBJECT_LOCK(allocator);
if (memory->sample && info->data == memory->sample->data) { InterlockedDecrement(&memory->sample->refcount); - pthread_cond_signal(&allocator->release_cond); + g_cond_signal(&allocator->release_cond); }
- pthread_mutex_unlock(&allocator->mutex); + GST_OBJECT_UNLOCK(allocator); }
static void wg_allocator_init(WgAllocator *allocator) @@ -129,8 +128,7 @@ static void wg_allocator_init(WgAllocator *allocator)
GST_OBJECT_FLAG_SET(allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
- pthread_mutex_init(&allocator->mutex, NULL); - pthread_cond_init(&allocator->release_cond, NULL); + g_cond_init(&allocator->release_cond); list_init(&allocator->memory_list); }
@@ -140,8 +138,7 @@ static void wg_allocator_finalize(GObject *object)
GST_LOG("allocator %p", allocator);
- pthread_cond_destroy(&allocator->release_cond); - pthread_mutex_destroy(&allocator->mutex); + g_cond_clear(&allocator->release_cond);
G_OBJECT_CLASS(wg_allocator_parent_class)->finalize(object); } @@ -160,12 +157,12 @@ static GstMemory *wg_allocator_alloc(GstAllocator *gst_allocator, gsize size, memory->unix_memory = gst_allocator_alloc(NULL, size, params); gst_memory_map(memory->unix_memory, &memory->unix_map_info, GST_MAP_WRITE);
- pthread_mutex_lock(&allocator->mutex); + GST_OBJECT_LOCK(allocator);
memory->sample = allocator->request_sample(size, allocator->request_sample_context); list_add_tail(&allocator->memory_list, &memory->entry);
- pthread_mutex_unlock(&allocator->mutex); + GST_OBJECT_UNLOCK(allocator);
GST_INFO("Allocated memory %p, sample %p, unix_memory %p, data %p", memory, memory->sample, memory->unix_memory, memory->unix_map_info.data); @@ -179,7 +176,7 @@ static void wg_allocator_free(GstAllocator *gst_allocator, GstMemory *gst_memory
GST_LOG("allocator %p, memory %p", allocator, memory);
- pthread_mutex_lock(&allocator->mutex); + GST_OBJECT_LOCK(allocator);
if (memory->sample) InterlockedDecrement(&memory->sample->refcount); @@ -187,7 +184,7 @@ static void wg_allocator_free(GstAllocator *gst_allocator, GstMemory *gst_memory
list_remove(&memory->entry);
- pthread_mutex_unlock(&allocator->mutex); + GST_OBJECT_UNLOCK(allocator);
gst_memory_unmap(memory->unix_memory, &memory->unix_map_info); gst_memory_unref(memory->unix_memory); @@ -228,7 +225,7 @@ static void release_memory_sample(WgAllocator *allocator, WgMemory *memory, bool while (sample->refcount > 1) { GST_WARNING("Waiting for sample %p to be unmapped", sample); - pthread_cond_wait(&allocator->release_cond, &allocator->mutex); + g_cond_wait(&allocator->release_cond, GST_OBJECT_GET_LOCK(allocator)); } InterlockedDecrement(&sample->refcount);
@@ -249,10 +246,10 @@ void wg_allocator_destroy(GstAllocator *gst_allocator)
GST_LOG("allocator %p", allocator);
- pthread_mutex_lock(&allocator->mutex); + GST_OBJECT_LOCK(allocator); LIST_FOR_EACH_ENTRY(memory, &allocator->memory_list, WgMemory, entry) release_memory_sample(allocator, memory, true); - pthread_mutex_unlock(&allocator->mutex); + GST_OBJECT_UNLOCK(allocator);
g_object_unref(allocator);
@@ -278,10 +275,10 @@ void wg_allocator_release_sample(GstAllocator *gst_allocator, struct wg_sample *
GST_LOG("allocator %p, sample %p, discard_data %u", allocator, sample, discard_data);
- pthread_mutex_lock(&allocator->mutex); + GST_OBJECT_LOCK(allocator); if ((memory = find_sample_memory(allocator, sample))) release_memory_sample(allocator, memory, discard_data); else if (sample->refcount) GST_ERROR("Couldn't find memory for sample %p", sample); - pthread_mutex_unlock(&allocator->mutex); + GST_OBJECT_UNLOCK(allocator); }
From: Rémi Bernon rbernon@codeweavers.com
Using the allocator lock and replacing the transform_request_sample callback with a default wg_allocator callback.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/unix_private.h | 2 ++ dlls/winegstreamer/wg_allocator.c | 42 +++++++++++++++++++++++++++++-- dlls/winegstreamer/wg_transform.c | 22 +++------------- 3 files changed, 45 insertions(+), 21 deletions(-)
diff --git a/dlls/winegstreamer/unix_private.h b/dlls/winegstreamer/unix_private.h index e9f472986ae..2bfdc6f9f5b 100644 --- a/dlls/winegstreamer/unix_private.h +++ b/dlls/winegstreamer/unix_private.h @@ -44,5 +44,7 @@ extern GstAllocator *wg_allocator_create(wg_allocator_request_sample_cb request_ extern void wg_allocator_destroy(GstAllocator *allocator) DECLSPEC_HIDDEN; extern void wg_allocator_release_sample(GstAllocator *allocator, struct wg_sample *sample, bool discard_data) DECLSPEC_HIDDEN; +extern void wg_allocator_set_next_sample(GstAllocator *allocator, + struct wg_sample *sample) DECLSPEC_HIDDEN;
#endif /* __WINE_WINEGSTREAMER_UNIX_PRIVATE_H */ diff --git a/dlls/winegstreamer/wg_allocator.c b/dlls/winegstreamer/wg_allocator.c index 16e961a57d4..53ea5d08c8e 100644 --- a/dlls/winegstreamer/wg_allocator.c +++ b/dlls/winegstreamer/wg_allocator.c @@ -54,6 +54,7 @@ typedef struct { GstAllocator parent;
+ struct wg_sample *next_sample; wg_allocator_request_sample_cb request_sample; void *request_sample_context;
@@ -68,6 +69,23 @@ typedef struct
G_DEFINE_TYPE(WgAllocator, wg_allocator, GST_TYPE_ALLOCATOR);
+static struct wg_sample *default_request_sample(gsize size, void *context) +{ + WgAllocator *allocator = context; + struct wg_sample *sample; + + GST_LOG("size %#zx, context %p", size, context); + + if (!(sample = allocator->next_sample)) + return NULL; + allocator->next_sample = NULL; + + if (sample->max_size < size) + return NULL; + + return sample; +} + static gpointer wg_allocator_map(GstMemory *gst_memory, GstMapInfo *info, gsize maxsize) { WgAllocator *allocator = (WgAllocator *)gst_memory->allocator; @@ -210,8 +228,14 @@ GstAllocator *wg_allocator_create(wg_allocator_request_sample_cb request_sample, if (!(allocator = g_object_new(wg_allocator_get_type(), NULL))) return NULL;
- allocator->request_sample = request_sample; - allocator->request_sample_context = request_sample_context; + if ((allocator->request_sample = request_sample)) + allocator->request_sample_context = request_sample_context; + else + { + allocator->request_sample = default_request_sample; + allocator->request_sample_context = allocator; + } + return GST_ALLOCATOR(allocator); }
@@ -282,3 +306,17 @@ void wg_allocator_release_sample(GstAllocator *gst_allocator, struct wg_sample * GST_ERROR("Couldn't find memory for sample %p", sample); GST_OBJECT_UNLOCK(allocator); } + +void wg_allocator_set_next_sample(GstAllocator *gst_allocator, struct wg_sample *sample) +{ + WgAllocator *allocator = (WgAllocator *)gst_allocator; + + GST_LOG("allocator %p, sample %p", allocator, sample); + + GST_OBJECT_LOCK(allocator); + if (allocator->next_sample) + InterlockedDecrement(&allocator->next_sample->refcount); + if ((allocator->next_sample = sample)) + InterlockedIncrement(&allocator->next_sample->refcount); + GST_OBJECT_UNLOCK(allocator); +} diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index e05432f6ac7..73c3c6d42b0 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -307,20 +307,6 @@ static bool transform_append_element(struct wg_transform *transform, GstElement return success; }
-static struct wg_sample *transform_request_sample(gsize size, void *context) -{ - struct wg_transform *transform = context; - struct wg_sample *sample; - - GST_LOG("size %#zx, context %p", size, transform); - - sample = InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL); - if (!sample || sample->max_size < size) - return NULL; - - return sample; -} - NTSTATUS wg_transform_create(void *args) { struct wg_transform_create_params *params = args; @@ -345,7 +331,7 @@ NTSTATUS wg_transform_create(void *args) goto out; if (!(transform->output_queue = gst_atomic_queue_new(8))) goto out; - if (!(transform->allocator = wg_allocator_create(transform_request_sample, transform))) + if (!(transform->allocator = wg_allocator_create(NULL, NULL))) goto out; transform->input_max_length = 1; transform->output_plane_align = 0; @@ -724,8 +710,7 @@ NTSTATUS wg_transform_read_data(void *args) NTSTATUS status;
/* Provide the sample for transform_request_sample to pick it up */ - InterlockedIncrement(&sample->refcount); - InterlockedExchangePointer((void **)&transform->output_wg_sample, sample); + wg_allocator_set_next_sample(transform->allocator, sample);
if (!gst_buffer_list_length(transform->input)) GST_DEBUG("Not input buffer queued"); @@ -741,8 +726,7 @@ NTSTATUS wg_transform_read_data(void *args) }
/* Remove the sample so transform_request_sample cannot use it */ - if (InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL)) - InterlockedDecrement(&sample->refcount); + wg_allocator_set_next_sample(transform->allocator, NULL);
if (ret) {
From: Rémi Bernon rbernon@codeweavers.com
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/wg_allocator.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/dlls/winegstreamer/wg_allocator.c b/dlls/winegstreamer/wg_allocator.c index 53ea5d08c8e..46343db8aae 100644 --- a/dlls/winegstreamer/wg_allocator.c +++ b/dlls/winegstreamer/wg_allocator.c @@ -80,9 +80,6 @@ static struct wg_sample *default_request_sample(gsize size, void *context) return NULL; allocator->next_sample = NULL;
- if (sample->max_size < size) - return NULL; - return sample; }
@@ -165,6 +162,7 @@ static GstMemory *wg_allocator_alloc(GstAllocator *gst_allocator, gsize size, GstAllocationParams *params) { WgAllocator *allocator = (WgAllocator *)gst_allocator; + struct wg_sample *sample; WgMemory *memory;
GST_LOG("allocator %p, size %#zx, params %p", allocator, size, params); @@ -177,7 +175,12 @@ static GstMemory *wg_allocator_alloc(GstAllocator *gst_allocator, gsize size,
GST_OBJECT_LOCK(allocator);
- memory->sample = allocator->request_sample(size, allocator->request_sample_context); + sample = allocator->request_sample(size, allocator->request_sample_context); + if (sample->max_size < size) + InterlockedDecrement(&sample->refcount); + else + memory->sample = sample; + list_add_tail(&allocator->memory_list, &memory->entry);
GST_OBJECT_UNLOCK(allocator);
From: Rémi Bernon rbernon@codeweavers.com
And push them one by one until an output buffer is generated, to avoid generating multiple output buffers without a backing wg_sample.
This makes zero-copy more efficient for games which queue multiple input buffers before checking output, such as Yakuza 4.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/wg_transform.c | 55 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 28 deletions(-)
diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 73c3c6d42b0..3ddeaeb44b3 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -51,10 +51,10 @@ struct wg_transform GstPad *my_src, *my_sink; GstPad *their_sink, *their_src; GstSegment segment; - GstBufferList *input; guint input_max_length; guint output_plane_align; struct wg_sample *output_wg_sample; + GstAtomicQueue *input_queue; GstAtomicQueue *output_queue; GstSample *output_sample; bool output_caps_changed; @@ -215,9 +215,11 @@ NTSTATUS wg_transform_destroy(void *args) { struct wg_transform *transform = args; GstSample *sample; + GstBuffer *buffer;
- if (transform->input) - gst_buffer_list_unref(transform->input); + while ((buffer = gst_atomic_queue_pop(transform->input_queue))) + gst_buffer_unref(buffer); + gst_atomic_queue_unref(transform->input_queue);
gst_element_set_state(transform->container, GST_STATE_NULL);
@@ -327,7 +329,7 @@ NTSTATUS wg_transform_create(void *args) return STATUS_NO_MEMORY; if (!(transform->container = gst_bin_new("wg_transform"))) goto out; - if (!(transform->input = gst_buffer_list_new())) + if (!(transform->input_queue = gst_atomic_queue_new(8))) goto out; if (!(transform->output_queue = gst_atomic_queue_new(8))) goto out; @@ -494,8 +496,8 @@ out: wg_allocator_destroy(transform->allocator); if (transform->output_queue) gst_atomic_queue_unref(transform->output_queue); - if (transform->input) - gst_buffer_list_unref(transform->input); + if (transform->input_queue) + gst_atomic_queue_unref(transform->input_queue); if (transform->container) { gst_element_set_state(transform->container, GST_STATE_NULL); @@ -521,7 +523,7 @@ NTSTATUS wg_transform_push_data(void *args) GstBuffer *buffer; guint length;
- length = gst_buffer_list_length(transform->input); + length = gst_atomic_queue_length(transform->input_queue); if (length >= transform->input_max_length) { GST_INFO("Refusing %u bytes, %u buffers already queued", sample->size, length); @@ -547,7 +549,7 @@ NTSTATUS wg_transform_push_data(void *args) GST_BUFFER_DURATION(buffer) = sample->duration * 100; if (!(sample->flags & WG_SAMPLE_FLAG_SYNC_POINT)) GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT); - gst_buffer_list_insert(transform->input, -1, buffer); + gst_atomic_queue_push(transform->input_queue, buffer);
params->result = S_OK; return STATUS_SUCCESS; @@ -702,9 +704,7 @@ NTSTATUS wg_transform_read_data(void *args) struct wg_transform *transform = params->transform; struct wg_sample *sample = params->sample; struct wg_format *format = params->format; - GstFlowReturn ret = GST_FLOW_OK; GstBuffer *output_buffer; - GstBufferList *input; GstCaps *output_caps; bool discard_data; NTSTATUS status; @@ -712,30 +712,29 @@ NTSTATUS wg_transform_read_data(void *args) /* Provide the sample for transform_request_sample to pick it up */ wg_allocator_set_next_sample(transform->allocator, sample);
- if (!gst_buffer_list_length(transform->input)) - GST_DEBUG("Not input buffer queued"); - else if ((input = gst_buffer_list_new())) - { - ret = gst_pad_push_list(transform->my_src, transform->input); - transform->input = input; - } - else + while (!transform->output_sample) { - GST_ERROR("Failed to allocate new input queue"); - ret = GST_FLOW_ERROR; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *input_buffer; + + if ((input_buffer = gst_atomic_queue_pop(transform->input_queue)) + && (ret = gst_pad_push(transform->my_src, input_buffer))) + { + GST_ERROR("Failed to push transform input, error %d", ret); + wg_allocator_set_next_sample(transform->allocator, NULL); + wg_allocator_release_sample(transform->allocator, sample, false); + return STATUS_UNSUCCESSFUL; + } + + transform->output_sample = gst_atomic_queue_pop(transform->output_queue); + if (!input_buffer) + break; }
/* Remove the sample so transform_request_sample cannot use it */ wg_allocator_set_next_sample(transform->allocator, NULL);
- if (ret) - { - GST_ERROR("Failed to push transform input, error %d", ret); - wg_allocator_release_sample(transform->allocator, sample, false); - return STATUS_UNSUCCESSFUL; - } - - if (!transform->output_sample && !(transform->output_sample = gst_atomic_queue_pop(transform->output_queue))) + if (!transform->output_sample) { sample->size = 0; params->result = MF_E_TRANSFORM_NEED_MORE_INPUT;
On Fri Jun 24 06:22:15 2022 +0000, **** wrote:
Zebediah Figura replied on the mailing list:
On 6/23/22 12:12, Rémi Bernon wrote: > @@ -291,6 +297,7 @@ enum unix_funcs > > unix_wg_transform_create, > unix_wg_transform_destroy, > + unix_wg_transform_set_format, > > unix_wg_transform_push_data, > unix_wg_transform_read_data, Perhaps set_output_format? Not that we're likely to need a set_input_format counterpart... > +NTSTATUS wg_transform_set_format(void *args) > +{ > + struct wg_transform_set_format_params *params = args; > + struct wg_transform *transform = params->transform; > + GstSample *sample; > + GstEvent *event; > + GstCaps *caps; > + gchar *str; > + > + if (!(caps = wg_format_to_caps(params->format))) > + { > + GST_ERROR("Failed to convert format to caps."); > + return STATUS_UNSUCCESSFUL; > + } > + > + if (gst_caps_is_always_compatible(transform->output_caps, caps)) > + { > + gst_caps_unref(caps); > + return STATUS_SUCCESS; > + } > + > + gst_caps_unref(transform->output_caps); > + transform->output_caps = caps; This isn't thread-safe; a simultaneous GST_EVENT_CAPS can modify the output caps. > + > + if (!gst_pad_set_caps(transform->my_sink, caps) The only effect of gst_pad_set_caps() is to send a CAPS event to the sink, but you've already set transform->output_caps above. Note also that the event won't be serialized, which begs the question—should it be? I suspect the answer is, "well, we actually need buffers already sent to be re-decoded in the new format, which GStreamer doesn't support". Which would be a helpful thing to write in a comment if so. Since we do flush output samples below, I suspect we should explicitly try to flush out samples which haven't been received yet before doing anything else in this function, which neatly solves all of the thread safety problems as well. I think the correct way to do that is with flush-start + flush-stop; a DRAIN query might work but I'm not sure if it's guaranteed to force decoders to stop waiting for more data before sending what they have.
I've left this part aside for a later MR instead.
On Fri Jun 24 06:22:15 2022 +0000, **** wrote:
Zebediah Figura replied on the mailing list:
On 6/23/22 12:12, Rémi Bernon wrote: > From: Rémi Bernon <rbernon@codeweavers.com> > > Signed-off-by: Rémi Bernon <rbernon@codeweavers.com> > --- > dlls/winegstreamer/wg_transform.c | 9 +++++++-- > 1 file changed, 7 insertions(+), 2 deletions(-) > > diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c > index e05432f6ac7..b0048fad644 100644 > --- a/dlls/winegstreamer/wg_transform.c > +++ b/dlls/winegstreamer/wg_transform.c > @@ -314,10 +314,15 @@ static struct wg_sample *transform_request_sample(gsize size, void *context) > > GST_LOG("size %#zx, context %p", size, transform); > > - sample = InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL); > - if (!sample || sample->max_size < size) > + if (!(sample = InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL))) > return NULL; > > + if (sample->max_size < size) > + { > + InterlockedDecrement(&sample->refcount); > + return NULL; > + } > + > return sample; > } > I'll sign off on this because it's an improvement over the current code, but on reflection I think this pattern is not very idiomatic. More idiomatic would be to protect the whole thing with a lock, and not set the pointer to NULL in this function (but instead add an extra reference).
I'm wary of adding locks, there's many threads involved in winegstreamer and it's hard to tell which combination of locks is safe to hold.
Then I've changed the code to use a default allocator callback and a new helper to provide samples one at a time, using the allocator lock only.
I think the correct way to do that is with flush-start + flush-stop; a DRAIN query might work but I'm not sure if it's guaranteed to force decoders to stop waiting for more data before sending what they have.
As an early note, I've tried both and to me it looks like the drain query is the best way here.
Flushing will apparently discard any decoder state as well, which we don't necessarily want. We only want the next buffers to be converted to the new format, and not necessarily wait until the next keyframe. For instance flushing causes the tests to fail when comparing the output image, while draining succeeds.
Draining will also eventually let us re-convert all the queued output buffers to the new format if we want to do that for compatibility at some point instead of discarding them.