FfmpegDecoderPlugin.cxx 17.1 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20 21 22
/* necessary because libavutil/common.h uses UINT64_C */
#define __STDC_CONSTANT_MACROS

23
#include "config.h"
24
#include "FfmpegDecoderPlugin.hxx"
25
#include "../DecoderAPI.hxx"
26
#include "FfmpegMetaData.hxx"
27
#include "tag/TagHandler.hxx"
Max Kellermann's avatar
Max Kellermann committed
28
#include "input/InputStream.hxx"
29
#include "CheckAudioFormat.hxx"
30
#include "util/Error.hxx"
31 32
#include "util/Domain.hxx"
#include "LogV.hxx"
33 34

extern "C" {
35 36 37
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
38
#include <libavutil/avutil.h>
39
#include <libavutil/log.h>
40
#include <libavutil/mathematics.h>
41 42 43 44

#if LIBAVUTIL_VERSION_MAJOR >= 53
#include <libavutil/frame.h>
#endif
45
}
46

47 48 49
#include <assert.h>
#include <string.h>

50
static constexpr Domain ffmpeg_domain("ffmpeg");
51

52 53 54 55 56
/* suppress the ffmpeg compatibility macro */
#ifdef SampleFormat
#undef SampleFormat
#endif

57 58
static LogLevel
import_ffmpeg_level(int level)
59 60
{
	if (level <= AV_LOG_FATAL)
61
		return LogLevel::ERROR;
62

63 64
	if (level <= AV_LOG_WARNING)
		return LogLevel::WARNING;
65 66

	if (level <= AV_LOG_INFO)
67
		return LogLevel::INFO;
68

69
	return LogLevel::DEBUG;
70 71 72
}

static void
73
mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level,
74 75
			const char *fmt, va_list vl)
{
76
	const AVClass * cls = nullptr;
77

78
	if (ptr != nullptr)
79 80
		cls = *(const AVClass *const*)ptr;

81
	if (cls != nullptr) {
82 83 84
		char domain[64];
		snprintf(domain, sizeof(domain), "%s/%s",
			 ffmpeg_domain.GetName(), cls->item_name(ptr));
85 86
		const Domain d(domain);
		LogFormatV(d, import_ffmpeg_level(level), fmt, vl);
87
	}
88 89
}

90
struct AvioStream {
91
	Decoder *const decoder;
92
	InputStream &input;
93

94
	AVIOContext *io;
95

96
	unsigned char buffer[8192];
97

98
	AvioStream(Decoder *_decoder, InputStream &_input)
99 100 101 102 103 104 105 106
		:decoder(_decoder), input(_input), io(nullptr) {}

	~AvioStream() {
		if (io != nullptr)
			av_free(io);
	}

	bool Open();
107
};
108

109 110
static int
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
111
{
112
	AvioStream *stream = (AvioStream *)opaque;
113

114 115
	return decoder_read(stream->decoder, stream->input,
			    (void *)buf, size);
116 117
}

118 119
static int64_t
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
120
{
121
	AvioStream *stream = (AvioStream *)opaque;
122 123

	if (whence == AVSEEK_SIZE)
124
		return stream->input.size;
125

126
	if (!stream->input.LockSeek(pos, whence, IgnoreError()))
127 128
		return -1;

129
	return stream->input.offset;
130 131
}

132 133
bool
AvioStream::Open()
134
{
135 136 137
	io = avio_alloc_context(buffer, sizeof(buffer),
				false, this,
				mpd_ffmpeg_stream_read, nullptr,
138
				input.seekable
139 140
				? mpd_ffmpeg_stream_seek : nullptr);
	return io != nullptr;
141 142
}

143 144 145 146 147 148 149 150 151 152 153
/**
 * API compatibility wrapper for av_open_input_stream() and
 * avformat_open_input().
 */
static int
mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
		      AVIOContext *pb,
		      const char *filename,
		      AVInputFormat *fmt)
{
	AVFormatContext *context = avformat_alloc_context();
154
	if (context == nullptr)
155 156 157 158
		return AVERROR(ENOMEM);

	context->pb = pb;
	*ic_ptr = context;
159
	return avformat_open_input(ic_ptr, filename, fmt, nullptr);
160 161
}

162
static bool
163
ffmpeg_init(gcc_unused const config_param &param)
164
{
165 166
	av_log_set_callback(mpd_ffmpeg_log_callback);

167
	av_register_all();
168
	return true;
169 170
}

171 172 173 174 175
static int
ffmpeg_find_audio_stream(const AVFormatContext *format_context)
{
	for (unsigned i = 0; i < format_context->nb_streams; ++i)
		if (format_context->streams[i]->codec->codec_type ==
176
		    AVMEDIA_TYPE_AUDIO)
177 178 179 180 181
			return i;

	return -1;
}

182
gcc_const
183 184 185 186 187
static double
time_from_ffmpeg(int64_t t, const AVRational time_base)
{
	assert(t != (int64_t)AV_NOPTS_VALUE);

188 189
	return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
		/ (double)1024;
190 191
}

192
gcc_const
193 194 195 196 197 198 199
static int64_t
time_to_ffmpeg(double t, const AVRational time_base)
{
	return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024},
			    time_base);
}

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
/**
 * Replace #AV_NOPTS_VALUE with the given fallback.
 */
static constexpr int64_t
timestamp_fallback(int64_t t, int64_t fallback)
{
	return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
		? t
		: fallback;
}

/**
 * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
 * zero.  We can't use AV_NOPTS_VALUE in calculations, and we simply
 * assume that the stream's start time is zero, which appears to be
 * the best way out of that situation.
 */
static int64_t
start_time_fallback(const AVStream &stream)
{
	return timestamp_fallback(stream.start_time, 0);
}

223
static void
224 225 226
copy_interleave_frame2(uint8_t *dest, uint8_t **src,
		       unsigned nframes, unsigned nchannels,
		       unsigned sample_size)
227
{
228 229 230 231 232 233
	for (unsigned frame = 0; frame < nframes; ++frame) {
		for (unsigned channel = 0; channel < nchannels; ++channel) {
			memcpy(dest, src[channel] + frame * sample_size,
			       sample_size);
			dest += sample_size;
		}
234 235 236
	}
}

237 238 239 240 241 242
/**
 * Copy PCM data from a AVFrame to an interleaved buffer.
 */
static int
copy_interleave_frame(const AVCodecContext *codec_context,
		      const AVFrame *frame,
243 244
		      uint8_t **output_buffer,
		      uint8_t **global_buffer, int *global_buffer_size)
245 246 247 248 249 250 251
{
	int plane_size;
	const int data_size =
		av_samples_get_buffer_size(&plane_size,
					   codec_context->channels,
					   frame->nb_samples,
					   codec_context->sample_fmt, 1);
252 253 254
	if (data_size <= 0)
		return data_size;

255 256
	if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
	    codec_context->channels > 1) {
257 258 259 260 261 262 263 264 265 266 267 268
		if(*global_buffer_size < data_size) {
			av_freep(global_buffer);

			*global_buffer = (uint8_t*)av_malloc(data_size);

			if (!*global_buffer)
				/* Not enough memory - shouldn't happen */
				return AVERROR(ENOMEM);
			*global_buffer_size = data_size;
		}
		*output_buffer = *global_buffer;
		copy_interleave_frame2(*output_buffer, frame->extended_data,
269 270 271
				       frame->nb_samples,
				       codec_context->channels,
				       av_get_bytes_per_sample(codec_context->sample_fmt));
272
	} else {
273
		*output_buffer = frame->extended_data[0];
274 275 276 277 278
	}

	return data_size;
}

279
static DecoderCommand
280
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
281
		   const AVPacket *packet,
282
		   AVCodecContext *codec_context,
283
		   const AVStream *stream,
284 285
		   AVFrame *frame,
		   uint8_t **buffer, int *buffer_size)
286
{
287
	if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
288
		decoder_timestamp(decoder,
289
				  time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
290
				  stream->time_base));
291

292
	AVPacket packet2 = *packet;
293

294
	uint8_t *output_buffer;
295

296 297
	DecoderCommand cmd = DecoderCommand::NONE;
	while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
298
		int audio_size = 0;
299 300
		int got_frame = 0;
		int len = avcodec_decode_audio4(codec_context,
301
						frame, &got_frame,
302 303 304
						&packet2);
		if (len >= 0 && got_frame) {
			audio_size = copy_interleave_frame(codec_context,
305
							   frame,
306 307
							   &output_buffer,
							   buffer, buffer_size);
308 309
			if (audio_size < 0)
				len = audio_size;
310
		}
311 312 313

		if (len < 0) {
			/* if error, we skip the frame */
314 315
			LogDefault(ffmpeg_domain,
				   "decoding failed, frame skipped");
316 317 318
			break;
		}

319 320
		packet2.data += len;
		packet2.size -= len;
321

322
		if (audio_size <= 0)
323
			continue;
324

325
		cmd = decoder_data(decoder, is,
326
				   output_buffer, audio_size,
327
				   codec_context->bit_rate / 1000);
328
	}
329
	return cmd;
330 331
}

332
gcc_const
333
static SampleFormat
334
ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
335
{
336
	switch (sample_fmt) {
337
	case AV_SAMPLE_FMT_S16:
338
	case AV_SAMPLE_FMT_S16P:
339
		return SampleFormat::S16;
340

341
	case AV_SAMPLE_FMT_S32:
342
	case AV_SAMPLE_FMT_S32P:
343
		return SampleFormat::S32;
344

345
	case AV_SAMPLE_FMT_FLTP:
346
		return SampleFormat::FLOAT;
347

348
	default:
349 350 351 352 353 354
		break;
	}

	char buffer[64];
	const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer),
						    sample_fmt);
355
	if (name != nullptr)
356 357 358
		FormatError(ffmpeg_domain,
			    "Unsupported libavcodec SampleFormat value: %s (%d)",
			    name, sample_fmt);
359
	else
360 361 362
		FormatError(ffmpeg_domain,
			    "Unsupported libavcodec SampleFormat value: %d",
			    sample_fmt);
363
	return SampleFormat::UNDEFINED;
364 365
}

366
static AVInputFormat *
367
ffmpeg_probe(Decoder *decoder, InputStream &is)
368 369 370 371 372 373
{
	enum {
		BUFFER_SIZE = 16384,
		PADDING = 16,
	};

374
	unsigned char buffer[BUFFER_SIZE];
375
	size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
376
	if (nbytes <= PADDING || !is.LockRewind(IgnoreError()))
377
		return nullptr;
378 379 380 381 382 383 384

	/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
	   beyond the declared buffer limit, which makes valgrind
	   angry; this workaround removes some padding from the buffer
	   size */
	nbytes -= PADDING;

385 386 387
	AVProbeData avpd;
	avpd.buf = buffer;
	avpd.buf_size = nbytes;
388
	avpd.filename = is.GetURI();
389

390
	return av_probe_input_format(&avpd, true);
391 392
}

393
static void
394
ffmpeg_decode(Decoder &decoder, InputStream &input)
395
{
396
	AVInputFormat *input_format = ffmpeg_probe(&decoder, input);
397
	if (input_format == nullptr)
398 399
		return;

400 401
	FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
		    input_format->name, input_format->long_name);
402

403
	AvioStream stream(&decoder, input);
404
	if (!stream.Open()) {
405
		LogError(ffmpeg_domain, "Failed to open stream");
406 407 408
		return;
	}

409
	//ffmpeg works with ours "fileops" helper
410
	AVFormatContext *format_context = nullptr;
411
	if (mpd_ffmpeg_open_input(&format_context, stream.io,
412
				  input.GetURI(),
413
				  input_format) != 0) {
414
		LogError(ffmpeg_domain, "Open failed");
415 416 417
		return;
	}

418
	const int find_result =
419
		avformat_find_stream_info(format_context, nullptr);
420
	if (find_result < 0) {
421
		LogError(ffmpeg_domain, "Couldn't find stream info");
422
		avformat_close_input(&format_context);
423 424
		return;
	}
425

426
	int audio_stream = ffmpeg_find_audio_stream(format_context);
427
	if (audio_stream == -1) {
428
		LogError(ffmpeg_domain, "No audio stream inside");
429
		avformat_close_input(&format_context);
430 431
		return;
	}
432

433 434 435
	AVStream *av_stream = format_context->streams[audio_stream];

	AVCodecContext *codec_context = av_stream->codec;
436
	if (codec_context->codec_name[0] != 0)
437 438
		FormatDebug(ffmpeg_domain, "codec '%s'",
			    codec_context->codec_name);
439

440
	AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
441 442

	if (!codec) {
443
		LogError(ffmpeg_domain, "Unsupported audio codec");
444
		avformat_close_input(&format_context);
445 446 447
		return;
	}

448
	const SampleFormat sample_format =
449
		ffmpeg_sample_format(codec_context->sample_fmt);
450 451 452
	if (sample_format == SampleFormat::UNDEFINED) {
		// (error message already done by ffmpeg_sample_format())
		avformat_close_input(&format_context);
453
		return;
454
	}
455

456
	Error error;
457 458
	AudioFormat audio_format;
	if (!audio_format_init_checked(audio_format,
459
				       codec_context->sample_rate,
460
				       sample_format,
461
				       codec_context->channels, error)) {
462
		LogError(error);
463
		avformat_close_input(&format_context);
464 465 466 467 468 469 470 471
		return;
	}

	/* the audio format must be read from AVCodecContext by now,
	   because avcodec_open() has been demonstrated to fill bogus
	   values into AVCodecContext.channels - a change that will be
	   reverted later by avcodec_decode_audio3() */

472
	const int open_result = avcodec_open2(codec_context, codec, nullptr);
473
	if (open_result < 0) {
474
		LogError(ffmpeg_domain, "Could not open codec");
475
		avformat_close_input(&format_context);
476
		return;
477 478
	}

479 480 481
	int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
		? format_context->duration / AV_TIME_BASE
		: 0;
482

483
	decoder_initialized(decoder, audio_format,
484
			    input.seekable, total_time);
485

486 487 488
#if LIBAVUTIL_VERSION_MAJOR >= 53
	AVFrame *frame = av_frame_alloc();
#else
489
	AVFrame *frame = avcodec_alloc_frame();
490
#endif
491
	if (!frame) {
492
		LogError(ffmpeg_domain, "Could not allocate frame");
493 494 495 496
		avformat_close_input(&format_context);
		return;
	}

497
	uint8_t *interleaved_buffer = nullptr;
498 499
	int interleaved_buffer_size = 0;

500
	DecoderCommand cmd;
501
	do {
502
		AVPacket packet;
Max Kellermann's avatar
Max Kellermann committed
503
		if (av_read_frame(format_context, &packet) < 0)
504 505 506
			/* end of file */
			break;

507 508
		if (packet.stream_index == audio_stream)
			cmd = ffmpeg_send_packet(decoder, input,
Max Kellermann's avatar
Max Kellermann committed
509
						 &packet, codec_context,
510
						 av_stream,
511 512
						 frame,
						 &interleaved_buffer, &interleaved_buffer_size);
513 514 515 516
		else
			cmd = decoder_get_command(decoder);

		av_free_packet(&packet);
517

518
		if (cmd == DecoderCommand::SEEK) {
519
			int64_t where =
520
				time_to_ffmpeg(decoder_seek_where(decoder),
521
					       av_stream->time_base) +
522
				start_time_fallback(*av_stream);
523

524
			if (av_seek_frame(format_context, audio_stream, where,
525
					  AVSEEK_FLAG_ANY) < 0)
526
				decoder_seek_error(decoder);
527 528
			else {
				avcodec_flush_buffers(codec_context);
529
				decoder_command_finished(decoder);
530
			}
531
		}
532
	} while (cmd != DecoderCommand::STOP);
533

534 535 536
#if LIBAVUTIL_VERSION_MAJOR >= 53
	av_frame_free(&frame);
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
537 538 539 540
	avcodec_free_frame(&frame);
#else
	av_freep(&frame);
#endif
541
	av_freep(&interleaved_buffer);
542

543
	avcodec_close(codec_context);
544
	avformat_close_input(&format_context);
545 546
}

547
//no tag reading in ffmpeg, check if playable
548
static bool
549
ffmpeg_scan_stream(InputStream &is,
550
		   const struct tag_handler *handler, void *handler_ctx)
551
{
552 553
	AVInputFormat *input_format = ffmpeg_probe(nullptr, is);
	if (input_format == nullptr)
554
		return false;
555

556 557
	AvioStream stream(nullptr, is);
	if (!stream.Open())
558
		return false;
559

560
	AVFormatContext *f = nullptr;
561
	if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(),
562
				  input_format) != 0)
563
		return false;
564

565
	const int find_result =
566
		avformat_find_stream_info(f, nullptr);
567 568
	if (find_result < 0) {
		avformat_close_input(&f);
569
		return false;
570 571
	}

572 573 574
	if (f->duration != (int64_t)AV_NOPTS_VALUE)
		tag_handler_invoke_duration(handler, handler_ctx,
					    f->duration / AV_TIME_BASE);
575

576
	ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
577 578
	int idx = ffmpeg_find_audio_stream(f);
	if (idx >= 0)
579 580
		ffmpeg_scan_dictionary(f->streams[idx]->metadata,
				       handler, handler_ctx);
581

582
	avformat_close_input(&f);
583
	return true;
584 585 586
}

/**
587 588 589 590
 * A list of extensions found for the formats supported by ffmpeg.
 * This list is current as of 02-23-09; To find out if there are more
 * supported formats, check the ffmpeg changelog since this date for
 * more formats.
591
 */
Max Kellermann's avatar
Max Kellermann committed
592
static const char *const ffmpeg_suffixes[] = {
593 594 595 596 597
	"16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif",
	"aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf",
	"atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
	"cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
	"eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
598 599 600
	"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
	"m4a", "m4b", "m4v",
	"mad",
601 602 603 604 605 606 607
	"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
	"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
	"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
	"ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra",
	"ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
	"sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
	"tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
608 609
	"vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv",
	"wve",
610
	nullptr
611 612
};

Max Kellermann's avatar
Max Kellermann committed
613
static const char *const ffmpeg_mime_types[] = {
614
	"application/flv",
615
	"application/m4a",
616 617 618 619 620
	"application/mp4",
	"application/octet-stream",
	"application/ogg",
	"application/x-ms-wmz",
	"application/x-ms-wmd",
621
	"application/x-ogg",
622 623 624 625 626 627
	"application/x-shockwave-flash",
	"application/x-shorten",
	"audio/8svx",
	"audio/16sv",
	"audio/aac",
	"audio/ac3",
628
	"audio/aiff"
629 630 631
	"audio/amr",
	"audio/basic",
	"audio/flac",
632 633
	"audio/m4a",
	"audio/mp4",
634 635 636 637 638
	"audio/mpeg",
	"audio/musepack",
	"audio/ogg",
	"audio/qcelp",
	"audio/vorbis",
639
	"audio/vorbis+ogg",
640 641 642 643 644 645 646 647 648 649 650 651
	"audio/x-8svx",
	"audio/x-16sv",
	"audio/x-aac",
	"audio/x-ac3",
	"audio/x-aiff"
	"audio/x-alaw",
	"audio/x-au",
	"audio/x-dca",
	"audio/x-eac3",
	"audio/x-flac",
	"audio/x-gsm",
	"audio/x-mace",
652
	"audio/x-matroska",
653 654
	"audio/x-monkeys-audio",
	"audio/x-mpeg",
655 656
	"audio/x-ms-wma",
	"audio/x-ms-wax",
657
	"audio/x-musepack",
658 659 660
	"audio/x-ogg",
	"audio/x-vorbis",
	"audio/x-vorbis+ogg",
661 662 663 664
	"audio/x-pn-realaudio",
	"audio/x-pn-multirate-realaudio",
	"audio/x-speex",
	"audio/x-tta"
665
	"audio/x-voc",
666 667 668 669 670 671 672 673
	"audio/x-wav",
	"audio/x-wma",
	"audio/x-wv",
	"video/anim",
	"video/quicktime",
	"video/msvideo",
	"video/ogg",
	"video/theora",
674
	"video/webm",
675 676 677 678 679 680 681
	"video/x-dv",
	"video/x-flv",
	"video/x-matroska",
	"video/x-mjpeg",
	"video/x-mpeg",
	"video/x-ms-asf",
	"video/x-msvideo",
682 683 684 685
	"video/x-ms-wmv",
	"video/x-ms-wvx",
	"video/x-ms-wm",
	"video/x-ms-wmx",
686 687 688 689 690 691
	"video/x-nut",
	"video/x-pva",
	"video/x-theora",
	"video/x-vid",
	"video/x-wmv",
	"video/x-xvid",
692 693 694 695 696 697

	/* special value for the "ffmpeg" input plugin: all streams by
	   the "ffmpeg" input plugin shall be decoded by this
	   plugin */
	"audio/x-mpd-ffmpeg",

698
	nullptr
699 700
};

701
const struct DecoderPlugin ffmpeg_decoder_plugin = {
702 703 704 705 706 707 708 709 710 711
	"ffmpeg",
	ffmpeg_init,
	nullptr,
	ffmpeg_decode,
	nullptr,
	nullptr,
	ffmpeg_scan_stream,
	nullptr,
	ffmpeg_suffixes,
	ffmpeg_mime_types
712
};