[FFmpeg-devel] [PATCH] libgme support
Clément Bœsch
ubitux at gmail.com
Sat Jun 29 13:11:58 CEST 2013
On Fri, Jun 28, 2013 at 10:44:35AM +0200, wm4 wrote:
> Apparently some people want this.
>
> libmodplug.c was used as base, and there are many similarities. libgme
> does support stream callbacks, but since the gme_load_custom() function
> wants a file size anyway and documentation says most formats read the
> whole file, it doesn't seem worth the trouble.
>
> I'm not sure whether the auto-detection is not too fragile. libgme
> checks the 4 bytes of the start of the file only. I'm returning
> AVPROBE_SCORE_MAX / 2 hoping to reduce potential breakage.
> From 058190554c0c80ab8c6af398c23fca5aab5fab41 Mon Sep 17 00:00:00 2001
> From: wm4 <nfxjfg at googlemail.com>
> Date: Fri, 28 Jun 2013 10:36:00 +0200
> Subject: [PATCH] lavf: add support for libgme
>
> ---
> configure | 4 +
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/libgme.c | 194 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 200 insertions(+)
> create mode 100644 libavformat/libgme.c
>
> diff --git a/configure b/configure
> index 3c2b196..efc7ad1 100755
> --- a/configure
> +++ b/configure
> @@ -206,6 +206,7 @@ External library support:
> --enable-libiec61883 enable iec61883 via libiec61883 [no]
> --enable-libilbc enable iLBC de/encoding via libilbc [no]
> --enable-libmodplug enable ModPlug via libmodplug [no]
> + --enable-libgme enable Game Music Emu via libgme [no]
> --enable-libmp3lame enable MP3 encoding via libmp3lame [no]
> --enable-libnut enable NUT (de)muxing via libnut,
> native (de)muxer exists [no]
> @@ -1171,6 +1172,7 @@ EXTERNAL_LIBRARY_LIST="
> libiec61883
> libilbc
> libmodplug
> + libgme
> libmp3lame
> libnut
> libopencore_amrnb
> @@ -1981,6 +1983,7 @@ libgsm_ms_encoder_deps="libgsm"
> libilbc_decoder_deps="libilbc"
> libilbc_encoder_deps="libilbc"
> libmodplug_demuxer_deps="libmodplug"
> +libgme_demuxer_deps="libgme"
> libmp3lame_encoder_deps="libmp3lame"
> libmp3lame_encoder_select="audio_frame_queue"
> libopencore_amrnb_decoder_deps="libopencore_amrnb"
> @@ -4127,6 +4130,7 @@ enabled libgsm && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
> done || die "ERROR: libgsm not found"; }
> enabled libilbc && require libilbc ilbc.h WebRtcIlbcfix_InitDecode -lilbc
> enabled libmodplug && require libmodplug libmodplug/modplug.h ModPlug_Load -lmodplug
> +enabled libgme && require libgme gme/gme.h gme_new_emu -lgme
> enabled libmp3lame && require "libmp3lame >= 3.98.3" lame/lame.h lame_set_VBR_quality -lmp3lame
> enabled libnut && require libnut libnut.h nut_demuxer_init -lnut
> enabled libopencore_amrnb && require libopencore_amrnb opencore-amrnb/interf_dec.h Decoder_Interface_init -lopencore-amrnb
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 20bf186..8c6df88 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -413,6 +413,7 @@ OBJS-$(CONFIG_YUV4MPEGPIPE_DEMUXER) += yuv4mpeg.o
>
> # external libraries
> OBJS-$(CONFIG_LIBMODPLUG_DEMUXER) += libmodplug.o
> +OBJS-$(CONFIG_LIBGME_DEMUXER) += libgme.o
I think 'G' belongs before 'M'
> OBJS-$(CONFIG_LIBNUT_DEMUXER) += libnut.o
> OBJS-$(CONFIG_LIBNUT_MUXER) += libnut.o
> OBJS-$(CONFIG_LIBQUVI_DEMUXER) += libquvi.o
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index d4a2d27..3c327d7 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -338,6 +338,7 @@ void av_register_all(void)
>
> /* external libraries */
> REGISTER_DEMUXER (LIBMODPLUG, libmodplug);
> + REGISTER_DEMUXER (LIBGME, libgme);
ditto
> REGISTER_MUXDEMUX(LIBNUT, libnut);
> REGISTER_DEMUXER (LIBQUVI, libquvi);
> REGISTER_PROTOCOL(LIBRTMP, librtmp);
> diff --git a/libavformat/libgme.c b/libavformat/libgme.c
> new file mode 100644
> index 0000000..fa16db2
> --- /dev/null
> +++ b/libavformat/libgme.c
> @@ -0,0 +1,194 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> +* @file
> +* libgme demuxer
> +*/
> +
> +#include <gme/gme.h>
> +#include "libavutil/avstring.h"
> +#include "libavutil/eval.h"
> +#include "libavutil/opt.h"
> +#include "avformat.h"
> +#include "internal.h"
> +
> +typedef struct GMEContext {
> + const AVClass *class;
> + Music_Emu *e;
> + gme_info_t *info; ///< selected track
> +
> + /* options */
> + int track_index;
> + int sample_rate;
> + int64_t max_size;
> +} GMEContext;
> +
> +#define OFFSET(x) offsetof(GMEContext, x)
> +#define D AV_OPT_FLAG_DECODING_PARAM
AUDIO flag too maybe?
> +static const AVOption options[] = {
> + {"track_index", "Track index that should be played", OFFSET(track_index), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, D},
It seems the current trending is to use small caps description: "set track
index ..."
> + {"sample_rate", "Sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64 = 48000}, 1000, 999999, D},
> + {"max_size", "Max file size supported (in bytes). Default is 50MB.",
> + OFFSET(max_size), AV_OPT_TYPE_INT64, {.i64 = 50 * 1024 * 1024}, 0, SIZE_MAX, D},
> + {NULL},
nit: pointless ','
> +};
> +
> +static void add_meta(AVFormatContext *s, const char *name, const char *value)
> +{
> + if (value && value[0])
> + av_dict_set(&s->metadata, name, value, 0);
> +}
> +
> +static int load_metadata(AVFormatContext *s)
> +{
> + GMEContext *gme = s->priv_data;
> + gme_info_t *info = gme->info;
> +
> + add_meta(s, "system", info->system);
> + add_meta(s, "game", info->game);
> + add_meta(s, "song", info->song);
> + add_meta(s, "author", info->author);
> + add_meta(s, "copyright", info->copyright);
> + add_meta(s, "comment", info->comment);
> + add_meta(s, "dumper", info->dumper);
> +
> + return 0;
> +}
> +
> +#define AUDIO_PKT_SIZE 512
> +#define SAMPLE_RATE
> +
> +static int read_header_gme(AVFormatContext *s)
> +{
> + AVStream *st;
> + AVIOContext *pb = s->pb;
> + GMEContext *gme = s->priv_data;
> + int64_t sz = avio_size(pb);
> + char *buf;
> +
> + if (sz < 0) {
> + av_log(s, AV_LOG_WARNING, "Could not determine file size\n");
> + sz = gme->max_size;
> + } else if (gme->max_size && sz > gme->max_size) {
> + sz = gme->max_size;
> + }
> +
> + buf = av_malloc(sz);
> + if (!buf)
> + return AVERROR(ENOMEM);
> + sz = avio_read(pb, buf, sz);
> +
> + // Data left means our buffer (the max_size option) is too small
> + if (avio_read(pb, &(char){0}, 1) == 1)
I'm not sure this syntax will work everywhere (I don't find any match in
the code base BTW); better use a dummy char.
> + return AVERROR_BUFFER_TOO_SMALL;
> +
> + if (gme_open_data(buf, sz, &gme->e, gme->sample_rate)) {
> + av_freep(&buf);
> + return AVERROR_INVALIDDATA;
> + }
> + av_freep(&buf);
> +
> + if (gme_track_info(gme->e, &gme->info, gme->track_index))
> + return AVERROR_STREAM_NOT_FOUND;
> +
> + if (gme_start_track(gme->e, gme->track_index))
> + return AVERROR_UNKNOWN;
> +
> + load_metadata(s);
> +
> + st = avformat_new_stream(s, NULL);
> + if (!st)
> + return AVERROR(ENOMEM);
> + avpriv_set_pts_info(st, 64, 1, 1000);
> + if (st->duration > 0)
> + st->duration = gme->info->length;
> + st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
> +#if AV_HAVE_BIGENDIAN
> + st->codec->codec_id = AV_CODEC_ID_PCM_S16BE;
> +#else
> + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE;
> +#endif
You can use AV_NE() here to simplify this.
> + st->codec->channels = 2;
> + st->codec->sample_rate = gme->sample_rate;
> +
> + return 0;
> +}
> +
> +static int read_packet_gme(AVFormatContext *s, AVPacket *pkt)
> +{
> + GMEContext *gme = s->priv_data;
> + int n_samples = AUDIO_PKT_SIZE / (2 * 2);
> +
> + if (gme_track_ended(gme->e))
> + return AVERROR_EOF;
> +
> + if (av_new_packet(pkt, AUDIO_PKT_SIZE) < 0)
> + return AVERROR(ENOMEM);
> +
> + if (gme_play(gme->e, n_samples, (short *)pkt->data))
> + return AVERROR(EIO);
> + pkt->size = AUDIO_PKT_SIZE;
> +
> + return 0;
> +}
> +
> +static int read_close_gme(AVFormatContext *s)
> +{
> + GMEContext *gme = s->priv_data;
> + if (gme->info)
> + gme_free_info(gme->info);
> + if (gme->e)
> + gme_delete(gme->e);
Those functions don't work with NULL pointers?
> + return 0;
> +}
> +
> +static int read_seek_gme(AVFormatContext *s, int stream_idx, int64_t ts, int flags)
> +{
> + GMEContext *gme = s->priv_data;
> + if (!gme_seek(gme->e, (int)ts))
> + return AVERROR_UNKNOWN;
AVERROR_EXTERNAL looks more appropriate
> + return 0;
> +}
> +
> +static int probe_gme(AVProbeData *p)
> +{
> + // Reads 4 bytes - returns "" if unknown format.
> + if (gme_identify_header(p->buf)[0])
> + return AVPROBE_SCORE_MAX / 2;
> + return 0;
> +}
> +
> +static const AVClass class_gme = {
> + .class_name = "Game Music Emu demuxer",
> + .item_name = av_default_item_name,
> + .option = options,
> + .version = LIBAVUTIL_VERSION_INT,
> +};
> +
> +AVInputFormat ff_libgme_demuxer = {
> + .name = "libgme",
> + .long_name = NULL_IF_CONFIG_SMALL("Game Music Emu demuxer"),
> + .priv_data_size = sizeof(GMEContext),
> + .read_probe = probe_gme,
> + .read_header = read_header_gme,
> + .read_packet = read_packet_gme,
> + .read_close = read_close_gme,
> + .read_seek = read_seek_gme,
> + .priv_class = &class_gme,
> +};
I'm assuming the library licensing is compatible, no more comment from me.
Thanks
--
Clément B.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 490 bytes
Desc: not available
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20130629/edda1442/attachment.asc>
More information about the ffmpeg-devel
mailing list