sigmaplayer/src/audio.cpp

1760 lines
37 KiB
C++

//////////////////////////////////////////////////////////////////////////
/**
* SigmaPlayer source project - Audio player source file.
* \file audio.cpp
* \author bombur
* \version 0.1
* \date 07.03.2007
*
* 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.
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*/
//////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <libsp/sp_misc.h>
#include <libsp/sp_msg.h>
#include <libsp/sp_fip.h>
#include <libsp/sp_khwl.h>
#include <libsp/sp_mpeg.h>
#include <libsp/sp_cdrom.h>
#include "script.h"
#include "player.h"
#include "media.h"
#define AUDIO_USE_FAST_OUTPUT
//#define AUDIO_USE_FAST_OUTPUT_PTR
#ifdef INTERNAL_AUDIO_PLAYER
#include <contrib/libmad/mad.h>
#define AUDIO_INTERNAL
#include "audio.h"
static bool audio_msg = true;
#define MSG if (audio_msg) msg
static Audio *audio = NULL;
static int num_packets = 0, info_cnt = 0, max_info_cnt = 32;
const int min_avg_num = 10;
const int mp3_mintmpbuf = 16384;
const int mp3_numtmpbuf = mp3_mintmpbuf*8;
typedef struct AudioDir
{
SPString path;
DIR *dir;
} AudioDir;
static SPClassicList<AudioDir> *audio_dir_list = NULL;
static SPDLinkedList<SPString> *audio_prev_list = NULL;
#ifdef WIN32
#define audio_lseek _lseeki64
#else
#define audio_lseek lseek
#endif
// return big-endian 16-bit
static inline WORD mp3_scale(mad_fixed_t sample)
{
// round
sample += (1L << (MAD_F_FRACBITS - 16));
// clip
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
// quantize
return (WORD)(((sample >> (MAD_F_FRACBITS + 1 - 8)) & 0xff) | ((sample >> 5) & 0xff00));
}
static int normal_mp3_output(BYTE *data, struct mad_pcm *pcm)
{
unsigned int nchannels, nsamples;
mad_fixed_t const *left_ch, *right_ch;
// pcm->samplerate contains the sampling frequency
nchannels = pcm->channels;
nsamples = pcm->length;
left_ch = pcm->samples[0];
right_ch = pcm->samples[1];
WORD *d = (WORD *)data;
if (nchannels == 2)
{
while (nsamples--)
{
*d++ = mp3_scale(*left_ch++);
*d++ = mp3_scale(*right_ch++);
}
} else
{
while (nsamples--)
{
// output sample(s) in 16-bit signed big-endian PCM
*d++ = mp3_scale(*left_ch++);
}
}
return (DWORD)d - (DWORD)data;
}
#ifdef AUDIO_USE_FAST_OUTPUT
static const DWORD and1_mask = 0x1FE00000, and2_mask = 0x1FE000;
static int fast_mp3_output(BYTE *data, struct mad_pcm *pcm)
{
unsigned int nchannels, nsamples;
mad_fixed_t const *left_ch, *right_ch;
// pcm->samplerate contains the sampling frequency
nchannels = pcm->channels;
nsamples = pcm->length;
left_ch = pcm->samples[0];
right_ch = pcm->samples[1];
register DWORD and1 = and1_mask, and2 = and2_mask;
if (nchannels == 2)
{
DWORD *bd = (DWORD *)data;
register DWORD d, d1, d2;
while (nsamples--)
{
d1 = *left_ch++;
d2 = *right_ch++;
d = (d1 & and1) >> 21;
d |= (d2 & and1) >> 5;
d |= (d2 & and2) << 11;
d |= (d1 & and2) >> 5;
*bd++ = d;
}
return (DWORD)bd - (DWORD)data;
} else
{
WORD *bw = (WORD *)data;
DWORD d, d1;
while (nsamples--)
{
d1 = *left_ch++;
d = (d1 & and1) >> 21;
d |= (d1 & and2) >> 5;
*bw++ = (WORD)d;
}
return (DWORD)bw - (DWORD)data;
}
}
#endif
////////////////////////////////////////////////////
Audio::Audio()
{
audio_numbufs = 226;
audio_bufsize = 4608;
audio_bufidx = 0;
audio_samplerate = audio_channels = -1;
samples_per_frame = 1152;
audio_frame_number = 0;
bitrate = 0;
out_bps = 1; // avoid div.zero
avg_bitrate = 0; avg_num = 0;
filesize = 0;
cur_offset = 0;
cur_tmpoffset = 0;
length = 0;
seek_offset = 0;
needs_resample = false;
resample_packet_size = 4096;
mp3_tmpbuf = NULL;
mp3_tmppos = 0; mp3_tmpstart = 0; mp3_tmpread = 0;
mp3_want_more = false;
ac3_scan_header = -1;
ac3_framesize = 0;
ac3_gatherinfo = true;
ac3_gather_always = false; // disabled by video player
is_partial_packet = false;
is_partial_next_packet = false;
cur_bitrate = cur_audio_format = -1;
cur_samples = cur_bits = cur_channels = -1;
playing = false;
stopping = false;
wait = false;
fast = false;
fd = -1;
saved_pts = 0;
mux_buf = NULL;
mux_numbufs = 8;
mux_bufsize = 32768;
audio_fmt = RIFF_AUDIO_UNKNOWN;
fmtex = NULL;
max_info_cnt = 32;
mp3_output = NULL;
}
Audio::~Audio()
{
SPSafeFree(mp3_tmpbuf);
SPSafeFree(mux_buf);
SPSafeFree(fmtex);
}
DWORD Audio::GetBE(BYTE *buf, int offset, int numbytes)
{
DWORD res = 0;
int nb_1 = numbytes - 1;
buf += offset;
for (int n = 0; n < numbytes; n++)
{
res |= (*buf++) << (nb_1-- << 3);
}
return res;
}
int Audio::SetVbrLength(int total_num_frames, int vbr_filesize)
{
length = (int)(INT64(1000) * total_num_frames * samples_per_frame / audio_samplerate);
if (vbr_filesize > 0 && (filesize == 0 || vbr_filesize < filesize))
filesize = vbr_filesize;
if (length > 0)
{
bitrate = (int)(filesize * 8000 / length);
}
if (fd >= 0)
{
script_totaltime_callback(length / 1000);
}
return 0;
}
int Audio::ParseXingHeader(BYTE *buf, int len)
{
if (audio_samplerate <= 0)
return -1;
int flags = GetBE(buf, 4, 4);
int offs = 8;
int total_num_frames = 0;
int vbr_filesize = 0;
if (flags & AUDIO_XING_HAS_NUMFRAMES)
{
if (offs + 4 > len)
return -1;
total_num_frames = GetBE(buf, offs, 4);
offs += 4;
}
if (flags & AUDIO_XING_HAS_FILESIZE)
{
if (offs + 4 > len)
return -1;
vbr_filesize = GetBE(buf, offs, 4);
offs += 4;
}
SetVbrLength(total_num_frames, vbr_filesize);
if (flags & AUDIO_XING_HAS_TOC)
{
int table_num = MIN(100, len - offs);
if (table_num < 1)
return -1;
toc_length = length;
seek_toc.SetN(table_num);
for (int i = 0; i < 100; i++, offs++)
seek_toc[i] = (DWORD)(filesize * GetBE(buf, offs, 1) / 256);
}
return 0;
}
int Audio::ParseVBRIHeader(BYTE *buf, int len)
{
if (audio_samplerate <= 0)
return -1;
if (len < 26)
return -1;
int total_num_frames = GetBE(buf, 14, 4);
int vbr_filesize = GetBE(buf, 10, 4);
SetVbrLength(total_num_frames, vbr_filesize);
int table_num = GetBE(buf, 18, 2) + 1;
int scale = GetBE(buf, 20, 2);
int numb = GetBE(buf, 22, 2);
int num_toc_frames = GetBE(buf, 24, 2);
table_num = MIN(table_num, (len - 26) / numb);
if (table_num < 1)
return 0;
toc_length = (int)(INT64(1000) * table_num * num_toc_frames * samples_per_frame / audio_samplerate);
seek_toc.SetN(table_num);
DWORD last_pos = 0;
for (int i = 0; i < table_num; i++)
seek_toc[i] = last_pos = last_pos + GetBE(buf, 26 + i * numb, numb) * scale;
return 0;
}
int Audio::ParseAC3Header(BYTE *buf, int len, bool fill_info)
{
int sample_rate, channels, lfe;
// fast&dirty way to get frame size
if (!fill_info)
return mpeg_parse_ac3_header(buf, len, NULL, NULL, NULL, NULL, &ac3_framesize);
if (mpeg_parse_ac3_header(buf, len, &bitrate, &sample_rate, &channels, &lfe, &ac3_framesize) < 0)
return -1;
if (bitrate > 0)
{
out_bps = bitrate / 8;
if (filesize > 0 && length == 0)
{
int new_length = (int)(filesize * 8000 / bitrate);
if (new_length != length)
{
length = new_length;
script_totaltime_callback(length / 1000);
}
}
}
if (fmtex == NULL)
{
fmtex = (RIFF_WAVEFORMATEX *)SPmalloc(sizeof(RIFF_WAVEFORMATEX));
memset(fmtex, 0, sizeof(RIFF_WAVEFORMATEX));
}
if (fmtex != NULL)
{
WORD nch = (WORD)((channels << 8) | lfe);
if (fmtex->n_samples_per_sec != (DWORD)sample_rate || fmtex->n_channels != nch)
{
fmtex->n_samples_per_sec = sample_rate;
fmtex->n_channels = nch;
audio_update_format_string();
}
}
if (!audio->ac3_gather_always)
audio->ac3_gatherinfo = false;
return 0;
}
int Audio::ParseMp3Packet(MpegPacket *packet, BYTE * &buf, int len)
{
mp3_stream.error = MAD_ERROR_NONE;
if (!wait && mp3_want_more)
{
int bl = len;
if (mp3_tmpread > 0)
{
mp3_tmpstart += mp3_tmpread;
if (mp3_tmpstart + mp3_mintmpbuf > mp3_numtmpbuf ||
mp3_tmppos + bl > mp3_numtmpbuf)
{
mp3_tmppos -= mp3_tmpstart;
memcpy(mp3_tmpbuf, mp3_tmpbuf + mp3_tmpstart, mp3_tmppos);
cur_tmpoffset += mp3_tmpstart;
mp3_tmpstart = 0;
}
}
if ((bl > 0 && bl < mp3_numtmpbuf) || mp3_tmpread > 0)
{
if (mp3_tmppos + bl > mp3_numtmpbuf)
bl = mp3_numtmpbuf - mp3_tmppos;
if (bl <= 0)
bl = 0;
else
{
// TODO: optimize - remove this memcpy!
memcpy(mp3_tmpbuf + mp3_tmppos, buf, bl);
}
mp3_tmppos += bl;
mad_stream_buffer(&mp3_stream, mp3_tmpbuf + mp3_tmpstart, mp3_tmppos - mp3_tmpstart);
mp3_want_more = false;
}
mp3_tmpread = 0;
}
do
{
mp3_stream.error = MAD_ERROR_NONE;
if (wait || mad_frame_decode(&mp3_frame, &mp3_stream) == 0)
{
if (!wait)
{
mp3_tmpstart = mp3_stream.this_frame - mp3_tmpbuf;
mp3_tmpread = mp3_stream.next_frame - mp3_stream.this_frame;
mp3_want_more = false;
}
if (mpeg_find_free_blocks(MPEG_BUFFER_2) == 0)
{
// we'll wait...
wait = true;
return 0;
}
wait = false;
BYTE *data = mpeg_getcurbuf(MPEG_BUFFER_2);
mad_synth_frame(&mp3_synth, &mp3_frame);
cur_offset = cur_tmpoffset + mp3_tmpstart;
if (audio->avg_num == 0)
{
seek_offset = cur_offset;
if (filesize > 0)
filesize -= seek_offset;
int hdrlen = mp3_stream.bufend - mp3_stream.ptr.byte;
if (hdrlen > 4)
{
if (mp3_frame.header.layer == 0)
audio->samples_per_frame = 384;
else if (mp3_frame.header.layer == 2 && mp3_frame.header.flags & MAD_FLAG_LSF_EXT)
audio->samples_per_frame = 576;
else
audio->samples_per_frame = 1152;
audio_samplerate = mp3_synth.pcm.samplerate;
if (strncasecmp((char *)mp3_stream.ptr.byte, "Xing", 4) == 0 ||
strncasecmp((char *)mp3_stream.ptr.byte, "Info", 4) == 0)
{
audio->ParseXingHeader((BYTE *)mp3_stream.ptr.byte, hdrlen);
}
else if (strncasecmp((char *)mp3_stream.ptr.byte, "VBRI", 4) == 0)
{
audio->ParseVBRIHeader((BYTE *)mp3_stream.ptr.byte, hdrlen);
}
}
audio_update_format_string();
}
audio->avg_bitrate = audio->avg_bitrate * audio->avg_num + mp3_frame.header.bitrate;
audio->avg_bitrate /= ++audio->avg_num;
if (length == 0 && audio->avg_bitrate > 0 && audio->avg_num >= min_avg_num)
{
if (filesize > 0)
{
length = (int)(filesize * 8000 / audio->avg_bitrate);
script_totaltime_callback(length / 1000);
}
if ((audio->avg_num - min_avg_num) % 1000 == 0)
audio_update_format_string();
}
memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4);
packet->pts = 0;
packet->nframeheaders = 0xffff;
packet->firstaccessunitpointer = 0;
packet->type = 1;
packet->pData = data;
#ifdef AUDIO_USE_FAST_OUTPUT_PTR
packet->size = mp3_output(data, &mp3_synth.pcm);
#else
#ifdef AUDIO_USE_FAST_OUTPUT
if (fast)
packet->size = fast_mp3_output(data, &mp3_synth.pcm);
else
#endif
packet->size = normal_mp3_output(data, &mp3_synth.pcm);
#endif
#if 0
{
FILE *fp;
//fp = fopen("out.raw", "ab");
//fwrite(packet->pData, packet->size, 1, fp);
fp = fopen("out.mp3", "ab");
fwrite(mp3_stream.this_frame, mp3_tmpread, 1, fp);
fclose(fp);
}
#endif
if (packet->size > 0)
{
if ((int)mp3_synth.pcm.samplerate != audio_samplerate ||
mp3_synth.pcm.channels != audio_channels)
{
MpegAudioPacketInfo defaudioparams;
defaudioparams.type = eAudioFormat_PCM;
defaudioparams.samplerate = mp3_synth.pcm.samplerate;
defaudioparams.numberofbitspersample = 16;
defaudioparams.numberofchannels = mp3_synth.pcm.channels;
defaudioparams.fromstream = TRUE;
mpeg_setaudioparams(&defaudioparams);
out_bps = defaudioparams.samplerate * defaudioparams.numberofbitspersample
* defaudioparams.numberofchannels / 8;
audio_samplerate = mp3_synth.pcm.samplerate;
audio_channels = mp3_synth.pcm.channels;
}
mpeg_setbufidx(MPEG_BUFFER_2, packet);
if (len == 0)
return 1;
return len;
}
}
else if (mp3_stream.error == MAD_ERROR_BUFLEN)
mp3_want_more = true;
} while (MAD_RECOVERABLE(mp3_stream.error));
return 0;
}
int Audio::ParsePCMPacket(MpegPacket *packet, BYTE * &buf, int len)
{
if (len <= 0)
return 0;
BYTE *out = buf;
int outlen = len, inlen = len;
if (needs_resample)
{
if (mpeg_find_free_blocks(MPEG_BUFFER_2) == 0)
{
wait = true;
return 0;
}
wait = false;
out = mpeg_getcurbuf(MPEG_BUFFER_2);
outlen = resample_packet_size;
int numread = mpeg_PCM_resample_to_LPCM(buf, inlen, out, &outlen);
if (numread <= 0)
return 0;
else
{
len = numread;
buf += numread;
}
} else
{
wait = false;
inlen = 0;
mpeg_PCM_to_LPCM(out, outlen);
}
memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4);
packet->type = 1;
packet->pData = out;
packet->size = outlen;
packet->nframeheaders = 0xffff;
packet->firstaccessunitpointer = 0;
// increase bufidx
mpeg_setbufidx(needs_resample ? MPEG_BUFFER_2 : MPEG_BUFFER_1, packet);
return len;
}
int Audio::ParseAC3Packet(MpegPacket *packet, BYTE * &buf, int len)
{
if (len <= 0)
{
return 0;
}
if (ac3_scan_header > 0 && len > 7 - ac3_scan_header)
{
memcpy(ac3_header + ac3_scan_header, buf, 7 - ac3_scan_header);
if (ParseAC3Header(ac3_header, 7, ac3_gatherinfo) < 0)
{
// frankly to say, the rest of saved header may contain the next sync.word,
// but we skip it to keep the code simple.
ac3_scan_header = -1;
} else
{
ac3_framesize -= ac3_scan_header;
ac3_scan_header = 0;
}
}
if (ac3_scan_header != 0)
{
BYTE *b = buf;
int l = len;
for (; l > 0; l--, b++)
{
if (*b == 0x0b)
{
if (l < 7)
{
ac3_scan_header = l;
memcpy(ac3_header, b, ac3_scan_header);
break;
}
if (ParseAC3Header(b, l, ac3_gatherinfo) == 0)
{
ac3_scan_header = 0;
ac3_framesize = (b - buf);
break;
}
}
}
}
is_partial_packet = is_partial_next_packet;
is_partial_next_packet = false;
if (ac3_scan_header == 0)
{
if (ac3_framesize >= 0)
{
int left = len;
BYTE *b = buf;
if (ac3_framesize < left)
{
// limit packet to current framesize, if it is set
len = ac3_framesize;
left -= ac3_framesize;
b += ac3_framesize;
ac3_framesize = 0;
// find *next* ac3 header
if (ParseAC3Header(b, left, ac3_gatherinfo) < 0)
{
if (left < 7)
{
// the header was just too short
ac3_scan_header = left;
memcpy(ac3_header, b, ac3_scan_header);
// send incomplete next header to the driver
len += left;
} else
{
// did not found, we'll try raw byte search...
msg("AC3: Warning! Frame sync lost!\n");
ac3_scan_header = -1;
return 0;
}
}
if (len == 0)
{
len = Min(ac3_framesize, left);
if (ac3_framesize >= left)
ac3_framesize -= left;
}
audio_frame_number++;
}
else
{
ac3_framesize -= left;
is_partial_next_packet = true;
}
}
}
memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4);
packet->type = 1;
packet->pData = buf;
packet->size = len;
#if 0
{
FILE *fp;
fp = fopen("out.ac3", "ab");
fwrite(packet->pData, packet->size, 1, fp);
fclose(fp);
}
#endif
#if 0
{ BYTE *pd = packet->pData;
static int num_audio_p = 0;
msg("- [%d, %d] ---------------------------------------------", num_audio_p++, packet->size);
for (int kk = 0; kk < packet->size; kk++)
{
if ((kk % 16) == 0) msg("\n");
msg("%02x ", *pd++);
}
msg("\n---\n");
fflush(stdout);
}
#endif
packet->nframeheaders = 0xffff;
packet->firstaccessunitpointer = 0;
#if 0
if (packet->pts != 0)
msg("[%d,%d]\t\tsize=%d\t\tpts=%d\n", num_packets, packet->type, packet->size, (int)packet->pts);
#endif
// increase bufidx
mpeg_setbufidx(MPEG_BUFFER_1, packet);
return len;
}
int Audio::ParseRawPacket(MpegPacket *packet, BYTE * &buf, int len)
{
if (len == 0)
return 0;
memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4);
packet->type = 1;
packet->pData = buf;
packet->size = len;
packet->nframeheaders = 0xffff;
packet->firstaccessunitpointer = 0;
// increase bufidx
mpeg_setbufidx(MPEG_BUFFER_1, packet);
return len;
}
int Audio::Seek(LONGLONG pos, int msecs)
{
if (pos < 0)
return -1;
khwl_stop();
audio_lseek(fd, pos + seek_offset, SEEK_SET);
cur_tmpoffset = pos;
audio_reset();
media_skip_buffer(NULL);
mpeg_setpts(INT64(90) * msecs);
return audio_continue_play();
}
///////////////////////////////////////////////////
RIFF_AUDIO_FORMAT audio_get_audio_format(int fmt)
{
switch (fmt)
{
case 0x50:
return RIFF_AUDIO_MP2;
case 0x55:
return RIFF_AUDIO_MP3;
case 0x0001:
return RIFF_AUDIO_PCM;
case 0x2000:
return RIFF_AUDIO_AC3;
case 0x2001:
return RIFF_AUDIO_DTS;
case 0x674f:
case 0x676f:
case 0x6750:
case 0x6770:
case 0x6751:
case 0x6771:
return RIFF_AUDIO_VORBIS;
}
return RIFF_AUDIO_UNKNOWN;
}
SPString audio_get_audio_format_string(RIFF_AUDIO_FORMAT fmt)
{
if (fmt < 7)
{
const char *afmt[] = { "Unknown", "None", "LPCM", "PCM", "MPEGL1", "MPEGL2", "MP3", "AC3", "DTS", "Vorbis" };
return afmt[fmt + 1];
}
return SPString();
}
int audio_init(RIFF_AUDIO_FORMAT fmt, bool fast)
{
if (audio != NULL)
audio_deinit();
audio = new Audio();
audio->audio_fmt = fmt;
bool need_2nd_buf = false;
if (audio->audio_fmt == RIFF_AUDIO_MP3 || audio->audio_fmt == RIFF_AUDIO_MP2)
{
mad_stream_init(&audio->mp3_stream);
mad_frame_init(&audio->mp3_frame);
mad_synth_init(&audio->mp3_synth);
audio->fast = fast;
if (fast)
{
audio->mp3_stream.options |= MAD_OPTION_HALFSAMPLERATE;
audio->mp3_output = &fast_mp3_output;
} else
{
audio->mp3_output = &normal_mp3_output;
}
// allocate MP3 frame buffer
audio->mp3_tmpbuf = (BYTE *)SPmalloc(mp3_numtmpbuf);
if (audio->mp3_tmpbuf == NULL)
{
msg_error("Audio: Cannot allocate stream buffer.\n");
return 0;
}
fip_write_special(FIP_SPECIAL_MP3, 1);
audio->audio_numbufs = 226;
audio->audio_bufsize = 4608;
need_2nd_buf = true;
}
else if (audio->audio_fmt == RIFF_AUDIO_PCM)
{
audio->needs_resample = mpeg_is_resample_needed() == TRUE;
if (audio->needs_resample)
{
audio->audio_numbufs = 250;
audio->audio_bufsize = audio->resample_packet_size;
need_2nd_buf = true;
}
}
else if (audio->audio_fmt == RIFF_AUDIO_AC3)
{
fip_write_special(FIP_SPECIAL_DOLBY, 1);
}
else if (audio->audio_fmt == RIFF_AUDIO_DTS)
{
fip_write_special(FIP_SPECIAL_DTS, 1);
}
if (need_2nd_buf)
mpeg_setbuffer(MPEG_BUFFER_2, BUF_BASE, audio->audio_numbufs, audio->audio_bufsize);
audio->audio_bufidx = 0;
audio_reset();
audio_update_format_string();
return 1;
}
int audio_reset()
{
if (audio != NULL)
{
audio->mp3_want_more = true;
audio->wait = false;
audio->mp3_tmppos = 0;
audio->mp3_tmpstart = 0;
audio->mp3_tmpread = 0;
}
return 0;
}
int audio_deinit()
{
if (audio != NULL)
{
if (audio->audio_fmt == RIFF_AUDIO_MP3 || audio->audio_fmt == RIFF_AUDIO_MP2)
{
mad_synth_finish(&audio->mp3_synth);
mad_frame_finish(&audio->mp3_frame);
mad_stream_finish(&audio->mp3_stream);
}
}
SPSafeDelete(audio);
return 1;
}
int audio_parse_packet(MpegPacket *packet, BYTE * &buf, int len)
{
if (audio != NULL)
{
switch (audio->audio_fmt)
{
case RIFF_AUDIO_MP3:
case RIFF_AUDIO_MP2:
return audio->ParseMp3Packet(packet, buf, len);
case RIFF_AUDIO_PCM:
return audio->ParsePCMPacket(packet, buf, len);
case RIFF_AUDIO_AC3:
return audio->ParseAC3Packet(packet, buf, len);
default:
return audio->ParseRawPacket(packet, buf, len);
}
}
return 0;
}
////////////////////////////////////////////////////////
int audio_play(char *filepath, bool is_folder, bool continue_next)
{
if (is_folder)
{
if (filepath != NULL)
audio_delete_filelist();
filepath = audio_get_next(filepath, continue_next);
if (filepath == NULL)
return -1;
}
if (filepath == NULL)
{
fip_write_special(FIP_SPECIAL_PLAY, 1);
fip_write_special(FIP_SPECIAL_PAUSE, 0);
mpeg_setspeed(MPEG_SPEED_NORMAL);
return 0;
}
if (strncasecmp(filepath, "/cdrom/", 7) != 0 && strncasecmp(filepath, "/hdd/", 5) != 0)
return -1;
MPEG_NUM_PACKETS = 512;
mpeg_init(MPEG_1, TRUE, TRUE, FALSE);
int ret = media_open(filepath, MEDIA_TYPE_AUDIO);
if (ret < 0)
{
msg_error("Audio: media open FAILED.\n");
return ret;
}
MSG("Audio: start...\n");
num_packets = 0;
info_cnt = 0;
audio->mux_buf = (BYTE *)SPmalloc(audio->mux_numbufs * audio->mux_bufsize);
if (audio->mux_buf == NULL)
{
msg_error("Audio: Cannot allocate input buffer.\n");
return -1;
}
mpeg_setbuffer(MPEG_BUFFER_1, audio->mux_buf, audio->mux_numbufs, audio->mux_bufsize);
// write dvd-specific FIP stuff...
const char *digits = " 00000";
fip_write_string(digits);
fip_write_special(FIP_SPECIAL_COLON1, 1);
fip_write_special(FIP_SPECIAL_COLON2, 1);
fip_write_special(FIP_SPECIAL_PLAY, 1);
fip_write_special(FIP_SPECIAL_PAUSE, 0);
script_video_info_callback("");
audio->playing = true;
audio->stopping = false;
mpeg_start();
if (is_folder)
{
player_update_source(filepath);
}
return 0;
}
int audio_continue_play()
{
fip_write_special(FIP_SPECIAL_PLAY, 1);
fip_write_special(FIP_SPECIAL_PAUSE, 0);
mpeg_play();
return 0;
}
void audio_update_info()
{
if (audio == NULL)
return;
static int old_secs = 0;
KHWL_TIME_TYPE displ;
displ.pts = 0;
displ.timeres = 90000;
khwl_getproperty(KHWL_TIME_SET, etimSystemTimeClock, sizeof(displ), &displ);
if ((LONGLONG)displ.pts != audio->saved_pts)
{
if ((LONGLONG)displ.pts >= 0)
{
audio->saved_pts = displ.pts;
char fip_out[10];
int secs = (int)(displ.pts / 90000);
if (secs < 0)
secs = 0;
if (secs >= 10*3600)
secs = 10*3600-1;
if (secs != old_secs)
{
script_time_callback(secs);
fip_out[0] = ' ';
fip_out[1] = ' ';
fip_out[2] = (char)((secs/3600) + '0');
int secs3600 = secs%3600;
fip_out[3] = (char)(((secs3600/60)/10) + '0');
fip_out[4] = (char)(((secs3600/60)%10) + '0');
fip_out[5] = (char)(((secs3600%60)/10) + '0');
fip_out[6] = (char)(((secs3600%60)%10) + '0');
fip_out[7] = '\0';
fip_write_string(fip_out);
old_secs = secs;
}
}
}
}
/// Advance playing
int audio_loop()
{
if (audio == NULL)
return 1;
if (audio->stopping)
{
if (mpeg_wait(TRUE) == 1)
{
return audio_stop() ? 1 : 0;
}
}
static BYTE *buf = NULL;
static MEDIA_EVENT event = MEDIA_EVENT_OK;
static int len = 0;
if (!audio->stopping)
{
if (audio_ready())
{
buf = NULL;
event = MEDIA_EVENT_OK;
len = 0;
int ret = media_get_next_block(&buf, &event, &len);
if (ret > 0 && buf == NULL)
{
msg_error("Audio: Not initialized. STOP!\n");
return 1;
}
if (ret == -1)
{
msg_error("Audio: Error getting next block!\n");
return 1;
}
else if (ret == 0) // wait...
{
return 0;
}
if (event == MEDIA_EVENT_STOP)
{
MSG("Audio: STOP Event triggered!\n");
mpeg_play_normal();
audio->stopping = true;
return 0;
}
}
// send multiple frames (packets) in one chunk
for (;;)
{
MpegPacket *packet = mpeg_feed_getlast();
if (packet == NULL) // well, it won't really help
return 0;
int numread = audio_parse_packet(packet, buf, len);
if (numread == 0)
break;
len -= numread;
buf += numread;
// increase bufidx
mpeg_feed(MPEG_FEED_AUDIO);
num_packets++;
}
}
if (info_cnt++ > max_info_cnt)
{
info_cnt = 0;
audio_update_info();
}
return 0;
}
BOOL audio_ready()
{
return audio == NULL || !audio->wait;
}
/// Pause playing
BOOL audio_pause()
{
fip_write_special(FIP_SPECIAL_PLAY, 0);
fip_write_special(FIP_SPECIAL_PAUSE, 1);
mpeg_setspeed(MPEG_SPEED_PAUSE);
return TRUE;
}
/// Stop playing
BOOL audio_stop(BOOL continue_next)
{
if (audio != NULL)
{
if (audio->playing)
{
mpeg_deinit();
audio->playing = false;
}
audio->stopping = false;
media_close();
audio_deinit();
// check for file list
if (audio_dir_list != NULL)
{
if (audio_play(NULL, true, continue_next == TRUE) >= 0)
return FALSE;
}
}
return TRUE;
}
int audio_get_bitrate()
{
if (audio == NULL)
return 0;
// use known/VBR rate
if (audio->bitrate > 0)
return audio->bitrate;
// use average/CBR rate
if (audio->avg_bitrate > 0 && audio->avg_num >= min_avg_num)
return audio->avg_bitrate;
return 0;
}
int audio_get_output_bps()
{
if (audio == NULL)
return 0;
if (!audio->is_partial_packet)
return audio->out_bps;
return 0;
}
/// Seek to given time and play
BOOL audio_seek(int seconds, int from)
{
if (audio == NULL)
return FALSE;
// use TOC to search
int msecs = seconds * 1000;
if (from == SEEK_CUR)
{
// get current pts
int cur_msecs = (int)(mpeg_getpts() / 90);
#if 0
// get stream pts
int br = audio_get_bitrate();
if (br > 0)
cur_msecs = (int)(audio->cur_offset * 8000 / br);
else
{
script_error_callback(SCRIPT_ERROR_INVALID);
return FALSE;
}
#endif
msecs += cur_msecs;
}
if (msecs < 0)
msecs = 0;
LONGLONG pos = -1;
// use TOC
if (from == SEEK_SET && audio->seek_toc.GetN() > 0 && audio->toc_length > 0 && msecs < audio->toc_length)
{
int numtoc = audio->seek_toc.GetN();
int idx = numtoc * msecs / audio->toc_length;
LONGLONG p1 = audio->seek_toc[idx];
LONGLONG p2 = idx < numtoc - 1 ? audio->seek_toc[idx + 1] : audio->filesize;
int delta_toc = audio->toc_length / numtoc;
pos = p1 + (p2 - p1) * (msecs - idx * delta_toc) / delta_toc;
if (audio->bitrate > 0)
msecs = (int)(pos * 8000 / audio->bitrate);
}
// use known/VBR rate
else
{
int br = audio_get_bitrate();
if (br > 0)
{
pos = (LONGLONG)msecs * br / 8000;
msecs = (int)(pos * 8000 / br);
} else
pos = -1;
}
if (pos < 0)
{
script_error_callback(SCRIPT_ERROR_INVALID);
return FALSE;
}
return audio->Seek(pos, msecs) == 0;
}
int audio_forward()
{
audio_seek(10, SEEK_CUR);
return 0;
}
int audio_rewind()
{
audio_seek(-10, SEEK_CUR);
return 0;
}
void audio_setdebug(BOOL ison)
{
audio_msg = ison == TRUE;
}
BOOL audio_getdebug()
{
return audio_msg;
}
// internal funcs used by media
BOOL audio_open(const char *filepath)
{
if (audio != NULL && audio->fd != -1)
audio_close();
script_time_callback(0);
script_audio_info_callback("");
script_video_info_callback("");
if (filepath == NULL)
return FALSE;
int fd = cdrom_open(filepath, O_RDONLY);
if (fd < 0)
{
msg_error("Audio: Cannot open file %s.\n", filepath);
return FALSE;
}
struct stat64 statbuf;
LONGLONG filesize = 0, cur_offs = 0;
if (cdrom_stat(filepath, &statbuf) >= 0)
filesize = statbuf.st_size;
// Read first 12 bytes and check that this is an AVI file
RIFF_AUDIO_FORMAT afmt = RIFF_AUDIO_UNKNOWN;
BYTE data[20];
if (read(fd, data, 16) != 16)
{
msg_error("Audio: Cannot read header.\n");
return FALSE;
}
RIFF_WAVEFORMATEX *fmtex = NULL;
if (strncasecmp((char *)data, "RIFF", 4) == 0 && strncasecmp((char *)data+8, "WAVE", 4) == 0)
{
MSG("Audio: * WAVE format detected!\n");
// read WAVE header
if (strncasecmp((char *)data+12, "fmt ", 4) == 0)
{
fmtex = audio_read_formatex(fd, NULL, 0);
if (fmtex != NULL)
afmt = audio_get_audio_format(fmtex->w_format_tag);
}
for (;;)
{
read(fd, data, 8);
if (strncasecmp((char *)data, "data", 4) == 0)
break;
else if (strncasecmp((char *)data, "fact", 4) == 0)
{
int size = GET_DWORD(data + 4);
audio_lseek(fd, size, SEEK_CUR);
}
else
{
audio_lseek(fd, -8, SEEK_CUR);
break;
}
}
cur_offs = audio_lseek(fd, 0, SEEK_CUR);
if (filesize > 0)
filesize -= cur_offs;
} else
{
cur_offs = 0;
SPString fp = filepath;
if (fp.FindNoCase(".mp3") >= 0)
{
MSG("Audio: * MP3 format detected!\n");
afmt = RIFF_AUDIO_MP3;
// skip ID3v2
if (strncasecmp((char *)data, "ID3", 3) == 0 && data[3] < 0xff && data[4] < 0xff)
{
int id3_size = GET_SYNCSAFE_DWORD(data + 6);
if (data[5] & 0x10) // footer present
id3_size += 10;
cur_offs = id3_size + 10;
}
}
else if (fp.FindNoCase(".mp2") >= 0)
{
MSG("Audio: * MPEGL2 format detected!\n");
afmt = RIFF_AUDIO_MP2;
}
else if (fp.FindNoCase(".mp1") >= 0 || fp.FindNoCase(".mpa") >= 0)
{
MSG("Audio: * MPEGL1 format detected!\n");
afmt = RIFF_AUDIO_MP1;
}
else if (fp.FindNoCase(".ac3") >= 0)
{
MSG("Audio: * AC-3 format detected!\n");
afmt = RIFF_AUDIO_AC3;
}
else if (fp.FindNoCase(".ogg") >= 0)
{
MSG("Audio: * OGG Vorbis format detected!\n");
afmt = RIFF_AUDIO_VORBIS;
}
audio_lseek(fd, cur_offs, SEEK_SET);
}
if (afmt == RIFF_AUDIO_UNKNOWN)
{
msg_error("Audio: Unknown audio format!\n");
return FALSE;
}
MSG("Audio: Setting audio params.\n");
audio_setaudioparams(afmt, fmtex, 1);
if (audio_init(afmt, false) == 0)
return -1;
audio->fd = fd;
audio->fmtex = fmtex;
audio->filesize = filesize;
audio->cur_offset = 0;
if (afmt == RIFF_AUDIO_LPCM || afmt == RIFF_AUDIO_PCM)
{
if (audio->fmtex != NULL)
{
audio->bitrate = (audio->fmtex->n_samples_per_sec
* audio->fmtex->w_bits_per_sample
* audio->fmtex->n_channels);
audio_update_format_string();
if (audio->bitrate > 0)
{
audio->length = (int)(filesize * 8000 / audio->bitrate);
script_totaltime_callback(audio->length / 1000);
}
}
}
return TRUE;
}
BOOL audio_close()
{
if (audio != NULL && audio->fd >= 0)
{
close(audio->fd);
audio->fd = -1;
return TRUE;
}
return FALSE;
}
int audio_read(BYTE *buf, int *len)
{
if (audio == NULL || buf == NULL || len == NULL || *len < 1)
return 0;
int n = 0, newlen = 0;
while (newlen < *len)
{
n = read (audio->fd, buf + newlen, *len - newlen);
if (n == 0)
break;
if (n < 0)
{
if (errno == EINTR)
continue;
else
break;
}
newlen += n;
}
if (newlen < 1) return 0;
*len = newlen;
return 1;
}
RIFF_WAVEFORMATEX *audio_read_formatex(int fd, BYTE *buf, int len)
{
RIFF_WAVEFORMATEX *wfe;
if (len <= 0)
{
if (read(fd, &len, 4) != 4)
return NULL;
}
if (len < 0 || len >= (long)sizeof(RIFF_WAVEFORMATEX))
len = sizeof(RIFF_WAVEFORMATEX);
wfe = (RIFF_WAVEFORMATEX *)SPmalloc(sizeof(RIFF_WAVEFORMATEX));
if (wfe != NULL)
{
memset(wfe, 0, sizeof(RIFF_WAVEFORMATEX));
if (buf != NULL)
memcpy(wfe, buf, len);
else
{
if (read(fd, wfe, len) != len)
{
SPfree(wfe);
return NULL;
}
}
if (wfe->cb_size != 0)
{
wfe = (RIFF_WAVEFORMATEX *)SPrealloc(wfe, sizeof(RIFF_WAVEFORMATEX) + wfe->cb_size);
if (wfe != NULL)
{
if (read(fd, (BYTE *)wfe + sizeof(RIFF_WAVEFORMATEX), wfe->cb_size) != wfe->cb_size)
{
SPfree(wfe);
return NULL;
}
}
}
}
return wfe;
}
void audio_update_format_string()
{
if (audio != NULL && audio->audio_fmt != RIFF_AUDIO_UNKNOWN)
{
int br = audio_get_bitrate();
if (br != audio->cur_bitrate || audio->audio_fmt != audio->cur_audio_format || (audio->fmtex != NULL
&& ((int)audio->fmtex->n_samples_per_sec != audio->cur_samples ||
(int)audio->fmtex->w_bits_per_sample != audio->cur_bits ||
(int)audio->fmtex->n_channels != audio->cur_channels
)))
{
audio->cur_audio_format = audio->audio_fmt;
audio->cur_bitrate = br;
if (audio->fmtex != NULL)
{
audio->cur_samples = audio->fmtex->n_samples_per_sec;
audio->cur_bits = audio->fmtex->w_bits_per_sample;
audio->cur_channels = audio->fmtex->n_channels;
}
SPString fmt = audio_get_audio_format_string(audio->audio_fmt);
audio->audio_fmt_str = fmt;
if ((audio->audio_fmt == RIFF_AUDIO_PCM || audio->audio_fmt == RIFF_AUDIO_LPCM)
&& audio->fmtex != NULL)
{
audio->audio_fmt_str.Printf(" @ %d Hz, %d bits %s",
audio->fmtex->n_samples_per_sec,
audio->fmtex->w_bits_per_sample,
audio->fmtex->n_channels > 1 ? "stereo" : "mono");
}
else
{
if (br > 0)
audio->audio_fmt_str.Printf(" @ %d kbps", (br + 500) / 1000);
}
if (audio->audio_fmt == RIFF_AUDIO_AC3 && audio->fmtex != NULL)
{
audio->audio_fmt_str.Printf(", %d Hz, ", audio->fmtex->n_samples_per_sec);
if (audio->fmtex->n_channels == 0x0100)
audio->audio_fmt_str.Printf("mono");
else if (audio->fmtex->n_channels == 0x0200)
audio->audio_fmt_str.Printf("stereo");
else
audio->audio_fmt_str.Printf("%d.%d", (audio->fmtex->n_channels >> 8) & 0xff,
audio->fmtex->n_channels & 0xff);
}
MSG("Audio: * Audio format detected: %s.\n", *audio->audio_fmt_str);
script_audio_info_callback(audio->audio_fmt_str);
}
}
}
int audio_setaudioparams(RIFF_AUDIO_FORMAT fmt, RIFF_WAVEFORMATEX *wfe, int halfrate)
{
MpegAudioPacketInfo defaudioparams;
// set audio format
switch (fmt)
{
case RIFF_AUDIO_MP2:
case RIFF_AUDIO_MP3:
defaudioparams.type = eAudioFormat_PCM;
if (wfe != NULL)
wfe->w_bits_per_sample = 16;
max_info_cnt = 20;
break;
case RIFF_AUDIO_AC3:
defaudioparams.type = eAudioFormat_AC3;
if (wfe != NULL)
wfe->w_bits_per_sample = 24;
max_info_cnt = 10;
break;
/* case RIFF_AUDIO_DTS:
defaudioparams.type = eAudioFormat_DTS;
break;
*/
case RIFF_AUDIO_PCM:
case RIFF_AUDIO_LPCM:
defaudioparams.type = eAudioFormat_PCM;
max_info_cnt = 32;
break;
default:
msg_error("AVI: Audio format %d not supported!\n", fmt);
return -1;
}
if (wfe != NULL)
{
if (wfe->n_samples_per_sec > 0 && wfe->w_bits_per_sample >= 8 && wfe->n_channels > 0)
{
defaudioparams.samplerate = wfe->n_samples_per_sec / halfrate;
defaudioparams.numberofbitspersample = wfe->w_bits_per_sample;
defaudioparams.numberofchannels = wfe->n_channels;
defaudioparams.fromstream = TRUE;
} else
return -1;
} else
{
defaudioparams.samplerate = 48000;
defaudioparams.numberofbitspersample = 24;
defaudioparams.numberofchannels = 2;
defaudioparams.fromstream = TRUE;
}
if (mpeg_setaudioparams(&defaudioparams) < 0)
{
return -1;
}
if (audio != NULL)
{
if (defaudioparams.type == eAudioFormat_AC3)
{
audio->out_bps = 0; // don't guess bitrate
audio->ac3_gatherinfo = true;
}
else
audio->out_bps = defaudioparams.samplerate * defaudioparams.numberofbitspersample
* defaudioparams.numberofchannels / 8;
if (audio->fmtex == NULL && wfe != NULL)
{
int wfe_size = sizeof(RIFF_WAVEFORMATEX) + wfe->cb_size;
audio->fmtex = (RIFF_WAVEFORMATEX *)SPmalloc(wfe_size);
if (audio->fmtex != NULL)
{
memcpy(audio->fmtex, wfe, wfe_size);
audio_update_format_string();
}
}
}
return 0;
}
void audio_set_ac3_getinfo(bool always)
{
if (audio != NULL)
audio->ac3_gather_always = always;
}
////////////////////////////////////////////////////////
char *audio_get_next(char *filepath, bool get_next)
{
static const char *audio_file_ext[] = { ".mp3", ".mp2", ".mp1", ".mpa", ".ac3", ".wav", ".ogg", NULL };
static char tmp_path[4096];
if (audio_dir_list == NULL)
{
if (filepath == NULL)
return NULL;
audio_dir_list = new SPClassicList<AudioDir>();
audio_prev_list = new SPDLinkedList<SPString>();
if (audio_dir_list == NULL || audio_prev_list == NULL)
return NULL;
audio_dir_list->Reserve(20);
audio_prev_list->SetSize(10);
tmp_path[0] = '\0';
AudioDir ad;
ad.dir = NULL;
ad.path = filepath;
audio_dir_list->Add(ad);
}
if (audio_dir_list->GetN() > 0)
{
if (!get_next)
{
if (audio_prev_list == NULL)
return NULL;
DLElement<SPString> *el = audio_prev_list->GetLast();
if (el == NULL)
return NULL;
strcpy(tmp_path, el->GetItem());
audio_prev_list->Delete(el);
return tmp_path;
}
for (;;)
{
AudioDir &ad = (*audio_dir_list)[audio_dir_list->GetN() - 1];
if (ad.dir == NULL)
{
// scan for files
ad.dir = cdrom_opendir(ad.path);
}
if (ad.dir != NULL)
{
struct dirent *d = cdrom_readdir(ad.dir);
if (d == NULL)
{
cdrom_closedir(ad.dir);
audio_dir_list->Remove(audio_dir_list->GetN() - 1);
if (audio_dir_list->GetN() < 1)
break;
continue;
}
if (d->d_name[0] == '.' && d->d_name[1] == '\0')
continue;
SPString path = ad.path + SPString("/") + SPString(d->d_name);
struct stat64 statbuf;
if (cdrom_stat(path, &statbuf) < 0)
{
msg("Cannot stat %s\n", *path);
continue;
}
if (S_ISDIR(statbuf.st_mode))
{
if (d->d_name[0] == '.') // hidden
continue;
AudioDir ad;
ad.dir = NULL;
ad.path = path;
audio_dir_list->Add(ad);
continue;
}
// check for file type
for (int k = 0; audio_file_ext[k] != NULL; k++)
{
if (strstr(d->d_name, audio_file_ext[k]) != NULL)
{
if (tmp_path[0] != '\0')
{
DLElement<SPString> *el = new DLElement<SPString>(tmp_path);
if (el == NULL)
return NULL;
audio_prev_list->Add(el);
}
strcpy(tmp_path, path);
msg("Audio: Playing %s...\n", tmp_path);
return tmp_path;
}
}
}
else
break;
}
}
audio_delete_filelist();
return NULL;
}
void audio_delete_filelist()
{
if (audio_dir_list != NULL)
{
for (int i = 0; i < audio_dir_list->GetN(); i++)
{
AudioDir &ad = (*audio_dir_list)[i];
cdrom_closedir(ad.dir);
}
SPSafeDelete(audio_dir_list);
}
if (audio_prev_list != NULL)
{
audio_prev_list->Delete();
SPSafeDelete(audio_prev_list);
}
}
#endif