[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