support mp3 decoding over libavcodec

This commit is contained in:
notaz 2013-07-01 03:40:31 +03:00
parent 90f0dedf83
commit fc11dd059b
9 changed files with 342 additions and 158 deletions

View file

@ -110,7 +110,12 @@ endif
endif # USE_FRONTEND endif # USE_FRONTEND
OBJS += platform/common/mp3.o
ifeq "$(HAVE_LIBAVCODEC)" "1"
OBJS += platform/common/mp3_libavcodec.o
else
OBJS += platform/common/mp3_dummy.o OBJS += platform/common/mp3_dummy.o
endif
# zlib # zlib
OBJS += zlib/gzio.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o \ OBJS += zlib/gzio.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o \

21
configure vendored
View file

@ -39,7 +39,7 @@ have_armv5=""
have_armv6="" have_armv6=""
have_armv7="" have_armv7=""
have_arm_neon="" have_arm_neon=""
have_tslib="" have_libavcodec=""
enable_dynarec="yes" enable_dynarec="yes"
need_sdl="no" need_sdl="no"
need_xlib="no" need_xlib="no"
@ -274,12 +274,26 @@ EOF
compile_binary "$@" compile_binary "$@"
} }
check_libavcodec()
{
cat > $TMPC <<EOF
#include <libavcodec/avcodec.h>
void main() { avcodec_decode_audio3(0, 0, 0, 0); }
EOF
compile_binary "$@" -lavcodec
}
#MAIN_LDLIBS="$MAIN_LDLIBS -lz" #MAIN_LDLIBS="$MAIN_LDLIBS -lz"
#check_zlib || fail "please install zlib (libz-dev)" #check_zlib || fail "please install zlib (libz-dev)"
MAIN_LDLIBS="-lpng $MAIN_LDLIBS" MAIN_LDLIBS="-lpng $MAIN_LDLIBS"
check_libpng || fail "please install libpng (libpng-dev)" check_libpng || fail "please install libpng (libpng-dev)"
if check_libavcodec; then
have_libavcodec="yes"
MAIN_LDLIBS="-lavcodec $MAIN_LDLIBS"
fi
# find what audio support we can compile # find what audio support we can compile
if [ "x$sound_drivers" = "x" ]; then if [ "x$sound_drivers" = "x" ]; then
if check_oss; then sound_drivers="$sound_drivers oss"; fi if check_oss; then sound_drivers="$sound_drivers oss"; fi
@ -320,6 +334,7 @@ fi
test "x$have_armv6" != "x" || have_armv6="no" test "x$have_armv6" != "x" || have_armv6="no"
test "x$have_armv7" != "x" || have_armv7="no" test "x$have_armv7" != "x" || have_armv7="no"
test "x$have_arm_neon" != "x" || have_arm_neon="no" test "x$have_arm_neon" != "x" || have_arm_neon="no"
test "x$have_libavcodec" != "x" || have_libavcodec="no"
echo "architecture $ARCH" echo "architecture $ARCH"
echo "platform $platform" echo "platform $platform"
@ -328,6 +343,7 @@ echo "C compiler $CC"
echo "C compiler flags $CFLAGS" echo "C compiler flags $CFLAGS"
echo "libraries $MAIN_LDLIBS" echo "libraries $MAIN_LDLIBS"
echo "linker flags $LDFLAGS" echo "linker flags $LDFLAGS"
echo "libavcodec (mp3) $have_libavcodec"
echo "enable dynarec $enable_dynarec" echo "enable dynarec $enable_dynarec"
# echo "ARMv7 optimizations $have_armv7" # echo "ARMv7 optimizations $have_armv7"
# echo "enable ARM NEON $have_arm_neon" # echo "enable ARM NEON $have_arm_neon"
@ -349,6 +365,9 @@ echo >> $config_mak
echo "ARCH = $ARCH" >> $config_mak echo "ARCH = $ARCH" >> $config_mak
echo "PLATFORM = $platform" >> $config_mak echo "PLATFORM = $platform" >> $config_mak
echo "SOUND_DRIVERS = $sound_drivers" >> $config_mak echo "SOUND_DRIVERS = $sound_drivers" >> $config_mak
if [ "$have_libavcodec" = "yes" ]; then
echo "HAVE_LIBAVCODEC = 1" >> $config_mak
fi
if [ "$have_arm_neon" = "yes" ]; then if [ "$have_arm_neon" = "yes" ]; then
echo "HAVE_NEON = 1" >> $config_mak echo "HAVE_NEON = 1" >> $config_mak
fi fi

View file

@ -60,6 +60,7 @@ include $(R)platform/common/common.mak
LOCAL_SRC_FILES += $(SRCS_COMMON) LOCAL_SRC_FILES += $(SRCS_COMMON)
LOCAL_SRC_FILES += $(R)platform/libretro.c LOCAL_SRC_FILES += $(R)platform/libretro.c
LOCAL_SRC_FILES += $(R)platform/common/mp3.c
LOCAL_SRC_FILES += $(R)platform/common/mp3_dummy.c LOCAL_SRC_FILES += $(R)platform/common/mp3_dummy.c
# zlib/unzip # zlib/unzip

View file

@ -1,12 +1,26 @@
/* /*
* PicoDrive * PicoDrive
* (C) notaz, 2010 * (C) notaz, 2010,2013
* *
* This work is licensed under the terms of MAME license. * This work is licensed under the terms of MAME license.
* See COPYING file in the top-level directory. * See COPYING file in the top-level directory.
*/ */
#include <stdio.h>
#include <string.h>
#include <pico/pico_int.h>
#include <pico/sound/mix.h>
#include "mp3.h" #include "mp3.h"
static FILE *mp3_current_file;
static int mp3_file_len, mp3_file_pos;
static int cdda_out_pos;
static int decoder_active;
unsigned short mpeg1_l3_bitrates[16] = {
0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320
};
int mp3_find_sync_word(const unsigned char *buf, int size) int mp3_find_sync_word(const unsigned char *buf, int size)
{ {
const unsigned char *p, *pe; const unsigned char *p, *pe;
@ -34,3 +48,152 @@ int mp3_find_sync_word(const unsigned char *buf, int size)
return -1; return -1;
} }
static int try_get_bitrate(unsigned char *buf, int buf_size)
{
int offs1, offs = 0;
int ret;
while (1)
{
offs1 = mp3_find_sync_word(buf + offs, buf_size - offs);
if (offs1 < 0)
return -2;
offs += offs1;
if (buf_size - offs < 4)
return -3;
// printf("trying header %08x\n", *(int *)(buf + offs));
ret = mpeg1_l3_bitrates[buf[offs + 2] >> 4];
if (ret > 0)
return ret;
}
return -2;
}
int mp3_get_bitrate(void *f_, int len)
{
unsigned char buf[2048];
FILE *f = f_;
int retval = -1;
int ret;
memset(buf, 0, sizeof(buf));
fseek(f, 0, SEEK_SET);
ret = fread(buf, 1, sizeof(buf), f);
if (ret != sizeof(buf))
goto out;
ret = try_get_bitrate(buf, sizeof(buf));
if (ret <= 0) {
// try to read somewhere around the middle
fseek(f, len / 2, SEEK_SET);
fread(buf, 1, sizeof(buf), f);
ret = try_get_bitrate(buf, sizeof(buf));
}
if (ret > 0)
retval = ret;
//printf("bitrate: %i\n", retval);
out:
fseek(f, 0, SEEK_SET);
return retval;
}
void mp3_start_play(void *f_, int pos)
{
unsigned char buf[2048];
FILE *f = f_;
int ret;
mp3_file_len = mp3_file_pos = 0;
mp3_current_file = NULL;
cdda_out_pos = 0;
decoder_active = 0;
if (!(PicoOpt & POPT_EN_MCD_CDDA) || f == NULL) // cdda disabled or no file?
return;
ret = mp3dec_start();
if (ret != 0)
return;
decoder_active = 1;
mp3_current_file = f;
fseek(f, 0, SEEK_END);
mp3_file_len = ftell(f);
// search for first sync word, skipping stuff like ID3 tags
while (mp3_file_pos < 128*1024) {
int offs, bytes;
fseek(f, mp3_file_pos, SEEK_SET);
bytes = fread(buf, 1, sizeof(buf), f);
if (bytes < 4)
break;
offs = mp3_find_sync_word(buf, bytes);
if (offs >= 0) {
mp3_file_pos += offs;
break;
}
mp3_file_pos += bytes - 3;
}
// seek..
if (pos) {
unsigned long long pos64 = mp3_file_len - mp3_file_pos;
pos64 *= pos;
mp3_file_pos += pos64 >> 10;
}
mp3dec_decode(mp3_current_file, &mp3_file_pos, mp3_file_len);
}
void mp3_update(int *buffer, int length, int stereo)
{
int length_mp3, shr = 0;
void (*mix_samples)(int *dest_buf, short *mp3_buf, int count) = mix_16h_to_32;
if (mp3_current_file == NULL || mp3_file_pos >= mp3_file_len)
return; /* no file / EOF */
if (!decoder_active)
return;
length_mp3 = length;
if (PsndRate <= 11025 + 100) {
mix_samples = mix_16h_to_32_s2;
length_mp3 <<= 2; shr = 2;
}
else if (PsndRate <= 22050 + 100) {
mix_samples = mix_16h_to_32_s1;
length_mp3 <<= 1; shr = 1;
}
if (1152 - cdda_out_pos >= length_mp3) {
mix_samples(buffer, cdda_out_buffer + cdda_out_pos * 2,
length * 2);
cdda_out_pos += length_mp3;
} else {
int ret, left = 1152 - cdda_out_pos;
mix_samples(buffer, cdda_out_buffer + cdda_out_pos * 2,
(left >> shr) * 2);
ret = mp3dec_decode(mp3_current_file, &mp3_file_pos,
mp3_file_len);
if (ret == 0) {
cdda_out_pos = length_mp3 - left;
mix_samples(buffer + (left >> shr) * 2,
cdda_out_buffer,
(cdda_out_pos >> shr) * 2);
} else
cdda_out_pos = 0;
}
}

View file

@ -1,8 +1,19 @@
#ifndef __COMMON_MP3_H__
#define __COMMON_MP3_H__
#include <stdio.h>
int mp3_find_sync_word(const unsigned char *buf, int size); int mp3_find_sync_word(const unsigned char *buf, int size);
/* decoder */
int mp3dec_start(void);
int mp3dec_decode(FILE *f, int *file_pos, int file_len);
extern unsigned short mpeg1_l3_bitrates[16];
#ifdef __GP2X__ #ifdef __GP2X__
void mp3_update_local(int *buffer, int length, int stereo); void mp3_update_local(int *buffer, int length, int stereo);
void mp3_start_play_local(void *f, int pos); void mp3_start_play_local(void *f, int pos);
#endif #endif
#endif // __COMMON_MP3_H__

View file

@ -6,18 +6,15 @@
* See COPYING file in the top-level directory. * See COPYING file in the top-level directory.
*/ */
#include <stdio.h>
#include "mp3.h" #include "mp3.h"
#include <pico/pico.h>
int mp3_get_bitrate(void *f_, int len) int mp3dec_start(void)
{ {
return -1; return -1;
} }
void mp3_start_play(void *f_, int pos) int mp3dec_decode(FILE *f, int *file_pos, int file_len)
{
}
void mp3_update(int *buffer, int length, int stereo)
{ {
return -1;
} }

View file

@ -16,74 +16,15 @@
#include "mp3.h" #include "mp3.h"
#include "lprintf.h" #include "lprintf.h"
static HMP3Decoder mp3dec = 0; static HMP3Decoder mp3dec;
static FILE *mp3_current_file = NULL; static unsigned char mp3_input_buffer[2 * 1024];
static int mp3_file_len = 0, mp3_file_pos = 0;
static int mp3_buffer_offs = 0;
static unsigned char mp3_input_buffer[2*1024];
#ifdef __GP2X__ #ifdef __GP2X__
#define mp3_update mp3_update_local #define mp3_update mp3_update_local
#define mp3_start_play mp3_start_play_local #define mp3_start_play mp3_start_play_local
#endif #endif
static int try_get_header(unsigned char *buff, MP3FrameInfo *fi) int mp3dec_decode(FILE *f, int *file_pos, int file_len)
{
int ret, offs1, offs = 0;
while (1)
{
offs1 = mp3_find_sync_word(buff + offs, 2048 - offs);
if (offs1 < 0) return -2;
offs += offs1;
if (2048 - offs < 4) return -3;
// printf("trying header %08x\n", *(int *)(buff + offs));
ret = MP3GetNextFrameInfo(mp3dec, fi, buff + offs);
if (ret == 0 && fi->bitrate != 0) break;
offs++;
}
return ret;
}
int mp3_get_bitrate(void *f_, int len)
{
unsigned char buff[2048];
MP3FrameInfo fi;
FILE *f = f_;
int ret;
memset(buff, 0, sizeof(buff));
if (mp3dec)
MP3FreeDecoder(mp3dec);
mp3dec = MP3InitDecoder();
fseek(f, 0, SEEK_SET);
ret = fread(buff, 1, sizeof(buff), f);
fseek(f, 0, SEEK_SET);
if (ret <= 0)
return -1;
ret = try_get_header(buff, &fi);
if (ret != 0 || fi.bitrate == 0) {
// try to read somewhere around the middle
fseek(f, len>>1, SEEK_SET);
fread(buff, 1, 2048, f);
fseek(f, 0, SEEK_SET);
ret = try_get_header(buff, &fi);
}
if (ret != 0)
return ret;
// printf("bitrate: %i\n", fi.bitrate / 1000);
return fi.bitrate / 1000;
}
static int mp3_decode(void)
{ {
unsigned char *readPtr; unsigned char *readPtr;
int bytesLeft; int bytesLeft;
@ -93,16 +34,17 @@ static int mp3_decode(void)
do do
{ {
if (mp3_file_pos >= mp3_file_len) if (*file_pos >= file_len)
return 1; /* EOF, nothing to do */ return 1; /* EOF, nothing to do */
fseek(mp3_current_file, mp3_file_pos, SEEK_SET); fseek(f, *file_pos, SEEK_SET);
bytesLeft = fread(mp3_input_buffer, 1, sizeof(mp3_input_buffer), mp3_current_file); bytesLeft = fread(mp3_input_buffer, 1, sizeof(mp3_input_buffer), f);
offset = mp3_find_sync_word(mp3_input_buffer, bytesLeft); offset = mp3_find_sync_word(mp3_input_buffer, bytesLeft);
if (offset < 0) { if (offset < 0) {
lprintf("find_sync_word (%i/%i) err %i\n", mp3_file_pos, mp3_file_len, offset); lprintf("find_sync_word (%i/%i) err %i\n",
mp3_file_pos = mp3_file_len; *file_pos, file_len, offset);
*file_pos = file_len;
return 1; // EOF return 1; // EOF
} }
readPtr = mp3_input_buffer + offset; readPtr = mp3_input_buffer + offset;
@ -113,112 +55,41 @@ static int mp3_decode(void)
if (err) { if (err) {
if (err == ERR_MP3_MAINDATA_UNDERFLOW && !had_err) { if (err == ERR_MP3_MAINDATA_UNDERFLOW && !had_err) {
// just need another frame // just need another frame
mp3_file_pos += readPtr - mp3_input_buffer; *file_pos += readPtr - mp3_input_buffer;
continue; continue;
} }
if (err == ERR_MP3_INDATA_UNDERFLOW && !had_err) { if (err == ERR_MP3_INDATA_UNDERFLOW && !had_err) {
if (offset == 0) if (offset == 0)
// something's really wrong here, frame had to fit // something's really wrong here, frame had to fit
mp3_file_pos = mp3_file_len; *file_pos = file_len;
else else
mp3_file_pos += offset; *file_pos += offset;
continue; continue;
} }
if (-12 <= err && err <= -6) { if (-12 <= err && err <= -6) {
// ERR_MP3_INVALID_FRAMEHEADER, ERR_MP3_INVALID_* // ERR_MP3_INVALID_FRAMEHEADER, ERR_MP3_INVALID_*
// just try to skip the offending frame.. // just try to skip the offending frame..
mp3_file_pos += offset + 1; *file_pos += offset + 1;
continue; continue;
} }
lprintf("MP3Decode err (%i/%i) %i\n", mp3_file_pos, mp3_file_len, err); lprintf("MP3Decode err (%i/%i) %i\n",
mp3_file_pos = mp3_file_len; *file_pos, file_len, err);
*file_pos = file_len;
return 1; return 1;
} }
mp3_file_pos += readPtr - mp3_input_buffer; *file_pos += readPtr - mp3_input_buffer;
} }
while (0); while (0);
return 0; return 0;
} }
void mp3_start_play(void *f_, int pos) int mp3dec_start(void)
{ {
FILE *f = f_;
mp3_file_len = mp3_file_pos = 0;
mp3_current_file = NULL;
mp3_buffer_offs = 0;
if (!(PicoOpt & POPT_EN_MCD_CDDA) || f == NULL) // cdda disabled or no file?
return;
// must re-init decoder for new track // must re-init decoder for new track
if (mp3dec) if (mp3dec)
MP3FreeDecoder(mp3dec); MP3FreeDecoder(mp3dec);
mp3dec = MP3InitDecoder(); mp3dec = MP3InitDecoder();
mp3_current_file = f; return (mp3dec == 0) ? -1 : 0;
fseek(f, 0, SEEK_END);
mp3_file_len = ftell(f);
// search for first sync word, skipping stuff like ID3 tags
while (mp3_file_pos < 128*1024) {
int offs, bytes;
fseek(f, mp3_file_pos, SEEK_SET);
bytes = fread(mp3_input_buffer, 1, sizeof(mp3_input_buffer), f);
if (bytes < 4)
break;
offs = mp3_find_sync_word(mp3_input_buffer, bytes);
if (offs >= 0) {
mp3_file_pos += offs;
break;
}
mp3_file_pos += bytes - 2;
}
// seek..
if (pos) {
unsigned long long pos64 = mp3_file_len - mp3_file_pos;
pos64 *= pos;
mp3_file_pos += pos64 >> 10;
}
mp3_decode();
} }
void mp3_update(int *buffer, int length, int stereo)
{
int length_mp3, shr = 0;
void (*mix_samples)(int *dest_buf, short *mp3_buf, int count) = mix_16h_to_32;
if (mp3_current_file == NULL || mp3_file_pos >= mp3_file_len)
return; /* no file / EOF */
length_mp3 = length;
if (PsndRate <= 11025 + 100) {
mix_samples = mix_16h_to_32_s2;
length_mp3 <<= 2; shr = 2;
}
else if (PsndRate <= 22050 + 100) {
mix_samples = mix_16h_to_32_s1;
length_mp3 <<= 1; shr = 1;
}
if (1152 - mp3_buffer_offs >= length_mp3) {
mix_samples(buffer, cdda_out_buffer + mp3_buffer_offs*2, length<<1);
mp3_buffer_offs += length_mp3;
} else {
int ret, left = 1152 - mp3_buffer_offs;
mix_samples(buffer, cdda_out_buffer + mp3_buffer_offs*2, (left>>shr)<<1);
ret = mp3_decode();
if (ret == 0) {
mp3_buffer_offs = length_mp3 - left;
mix_samples(buffer + ((left>>shr)<<1), cdda_out_buffer, (mp3_buffer_offs>>shr)<<1);
} else
mp3_buffer_offs = 0;
}
}

View file

@ -0,0 +1,117 @@
/*
* Some mp3 related code for Sega/Mega CD.
* Uses Libav/FFmpeg libavcodec
* (C) notaz, 2013
*
* This work is licensed under the terms of MAME license.
* See COPYING file in the top-level directory.
*/
#include <stdio.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <pico/pico_int.h>
#include "../libpicofe/lprintf.h"
#include "mp3.h"
static AVCodecContext *ctx;
int mp3dec_decode(FILE *f, int *file_pos, int file_len)
{
unsigned char input_buf[2 * 1024];
int frame_size;
AVPacket avpkt;
int bytes_in;
int bytes_out;
int offset;
int len;
av_init_packet(&avpkt);
do
{
if (*file_pos >= file_len)
return 1; // EOF, nothing to do
fseek(f, *file_pos, SEEK_SET);
bytes_in = fread(input_buf, 1, sizeof(input_buf), f);
offset = mp3_find_sync_word(input_buf, bytes_in);
if (offset < 0) {
lprintf("find_sync_word (%i/%i) err %i\n",
*file_pos, file_len, offset);
*file_pos = file_len;
return 1; // EOF
}
// to avoid being flooded with "incorrect frame size" errors,
// we must calculate and pass exact frame size - lame
frame_size = mpeg1_l3_bitrates[input_buf[offset + 2] >> 4];
frame_size = frame_size * 144000 / 44100;
frame_size += (input_buf[offset + 2] >> 1) & 1;
if (offset > 0 && bytes_in - offset < frame_size) {
// underflow
*file_pos += offset;
continue;
}
avpkt.data = input_buf + offset;
avpkt.size = frame_size;
bytes_out = sizeof(cdda_out_buffer);
len = avcodec_decode_audio3(ctx, cdda_out_buffer,
&bytes_out, &avpkt);
if (len <= 0) {
lprintf("mp3 decode err (%i/%i) %i\n",
*file_pos, file_len, len);
// attempt to skip the offending frame..
*file_pos += offset + 1;
continue;
}
*file_pos += offset + len;
}
while (0);
return 0;
}
int mp3dec_start(void)
{
AVCodec *codec;
int ret;
if (ctx != NULL)
return 0;
// init decoder
//avcodec_init();
avcodec_register_all();
// AV_CODEC_ID_MP3 ?
codec = avcodec_find_decoder(CODEC_ID_MP3);
if (codec == NULL) {
lprintf("mp3dec: codec missing\n");
return -1;
}
ctx = avcodec_alloc_context();
if (ctx == NULL) {
lprintf("mp3dec: avcodec_alloc_context failed\n");
return -1;
}
ret = avcodec_open(ctx, codec);
if (ret < 0) {
lprintf("mp3dec: avcodec_open failed: %d\n", ret);
av_free(ctx);
ctx = NULL;
return -1;
}
return 0;
}

View file

@ -217,7 +217,7 @@ void plat_init(void)
plat_sdl_quit_cb = plat_sdl_quit; plat_sdl_quit_cb = plat_sdl_quit;
SDL_WM_SetCaption("PicoDrive" VERSION, NULL); SDL_WM_SetCaption("PicoDrive " VERSION, NULL);
g_menuscreen_w = plat_sdl_screen->w; g_menuscreen_w = plat_sdl_screen->w;
g_menuscreen_h = plat_sdl_screen->h; g_menuscreen_h = plat_sdl_screen->h;