diff --git a/src/driver/audiodec/Makefile.am b/src/driver/audiodec/Makefile.am index 4fde05396..a837cd020 100644 --- a/src/driver/audiodec/Makefile.am +++ b/src/driver/audiodec/Makefile.am @@ -32,6 +32,7 @@ libneutrino_driver_audiodec_a_SOURCES = \ basedec.cpp \ cdrdec.cpp \ crc.c \ + ffmpegdec.cpp \ $(FLACdec) \ mp3dec.cpp \ oggdec.cpp \ diff --git a/src/driver/audiodec/basedec.cpp b/src/driver/audiodec/basedec.cpp index c38752464..88d0746e2 100644 --- a/src/driver/audiodec/basedec.cpp +++ b/src/driver/audiodec/basedec.cpp @@ -46,6 +46,7 @@ #include "mp3dec.h" #include "oggdec.h" #include "wavdec.h" +#include "ffmpegdec.h" #include unsigned int CBaseDec::mSamplerate=0; @@ -92,12 +93,18 @@ CBaseDec::RetCode CBaseDec::DecoderBase(CAudiofile* const in, &in->MetaData, t, secondsToSkip ); } - else + else if (ftype(fp, "mpeg")) { Status = CMP3Dec::getInstance()->Decoder( fp, OutputFd, state, &in->MetaData, t, secondsToSkip ); } + else + { + Status = CFfmpegDec::getInstance()->Decoder( fp, OutputFd, state, + &in->MetaData, t, + secondsToSkip ); + } } else if( in->FileType == CFile::FILE_MP3) { @@ -133,9 +140,9 @@ CBaseDec::RetCode CBaseDec::DecoderBase(CAudiofile* const in, #endif else { - fprintf( stderr, "DecoderBase: Supplied filetype is not " ); - fprintf( stderr, "supported by Audioplayer.\n" ); - Status = INTERNAL_ERR; + Status = CFfmpegDec::getInstance()->Decoder(fp, OutputFd, state, + &in->MetaData, t, + secondsToSkip ); } if ( fclose( fp ) == EOF ) @@ -260,3 +267,4 @@ void CBaseDec::Init() mSamplerate=0; } +// vim:ts=4 diff --git a/src/driver/audiodec/ffmpegdec.cpp b/src/driver/audiodec/ffmpegdec.cpp new file mode 100644 index 000000000..158bad6f7 --- /dev/null +++ b/src/driver/audiodec/ffmpegdec.cpp @@ -0,0 +1,324 @@ +/* + Neutrino-GUI - DBoxII-Project + + Copyright (C) 2004 Zwen + Copyright (C) 2013 martii + + ffmpeg audio decoder layer + Homepage: http://forum.tuxbox.org/forum + + License: GPL + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "ffmpegdec.h" +extern "C" { +#include +#include +#include +#include +} +#include + +extern cAudio * audioDecoder; + +#define ProgName "FfmpegDec" + +static void log_callback(void*, int, const char*format, va_list ap) +{ + vfprintf(stderr, format, ap); + +} + +CFfmpegDec::CFfmpegDec(void) +{ + av_log_set_callback(log_callback); + meta_data_valid = false; + buffer_size = 0x1000; + buffer = NULL; + avc = NULL; +} + +CFfmpegDec::~CFfmpegDec(void) +{ +} + +int CFfmpegDec::Read(void *buf, size_t buf_size) +{ + return fread(buf, buf_size, 1, (FILE *) in); +} + +static int read_packet(void *opaque, uint8_t *buf, int buf_size) +{ + int res = ((CFfmpegDec *) opaque)->Read(buf, (size_t) buf_size); + return res; +} + +int64_t CFfmpegDec::Seek(int64_t offset, int whence) +{ + return (int64_t) fseek((FILE *) in, (long) offset, whence); +} + +static int64_t seek_packet(void *opaque, int64_t offset, int whence) +{ + return ((CFfmpegDec *) opaque)->Seek(offset, whence); +} + +bool CFfmpegDec::Init(void) +{ + AVIOContext *avioc = NULL; + if (!avc) { + buffer = (unsigned char *) av_malloc(buffer_size); + if (!buffer) + return false; + avcodec_register_all(); + av_register_all(); + avc = avformat_alloc_context(); + if (!avc) + return false; + + avc->probesize = 128 * 1024; + + avioc = avio_alloc_context (buffer, buffer_size, 0, this, read_packet, NULL, seek_packet); + if (!avioc) { + av_freep(&buffer); + avformat_free_context(avc); + return false; + } + avc->pb = avioc; + avc->flags = AVFMT_FLAG_CUSTOM_IO; + if (!avformat_open_input(&avc, "", NULL, 0)) + return true; + } + if (avioc) + av_freep(avioc); + avc = NULL; + return false; +} + +void CFfmpegDec::DeInit(void) +{ + if (avc) { + avformat_close_input(&avc); +#if 0 + av_freep(&avc->pb); +#endif + avformat_free_context(avc); + avc = NULL; + } +#if 0 + if (buffer) { + av_freep(&buffer); + } +#endif +} + +CBaseDec::RetCode CFfmpegDec::Decoder(FILE *_in, int /*OutputFd*/, State* state, CAudioMetaData* _meta_data, time_t* time_played, unsigned int* /*secondsToSkip*/) +{ + in = _in; + RetCode Status=OK; + + if (!Init()) { + Status=DATA_ERR; + return Status; + } + + + if (!SetMetaData((FILE *)in, _meta_data)) { + DeInit(); + Status=DATA_ERR; + return Status; + } + + AVCodecContext *c = avc->streams[best_stream]->codec; + + if(avcodec_open2(c, codec, NULL)) + { + DeInit(); + Status=DATA_ERR; + return Status; + } + + SwrContext *swr = swr_alloc(); + if (!swr) { + avcodec_close(c); + DeInit(); + Status=DATA_ERR; + return Status; + } + + meta_data_valid = true; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + audioDecoder->PrepareClipPlay(mChannels, mSampleRate, 32, 1); +#else + audioDecoder->PrepareClipPlay(mChannels, mSampleRate, 32, 0); +#endif + + AVFrame *frame = NULL; + AVPacket packet; + av_init_packet(&packet); + + av_opt_set_int(swr, "in_channel_layout", c->channel_layout, 0); + av_opt_set_int(swr, "out_channel_layout", c->channel_layout, 0); + av_opt_set_int(swr, "in_sample_rate", c->sample_rate, 0); + av_opt_set_int(swr, "out_sample_rate", c->sample_rate, 0); + av_opt_set_int(swr, "in_sample_fmt", c->sample_fmt, 0); + av_opt_set_int(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + + swr_init(swr); + + uint8_t *outbuf = NULL; + int outsamples = 0; + int outsamples_max = 0; + + time_t starttime = time(NULL); + + do + { + while(*state==PAUSE) + usleep(10000); + + if (av_read_frame(avc, &packet)) { + Status=DATA_ERR; + break; + } + + if (packet.stream_index != best_stream) + continue; + + while (packet.size > 0) { + int got_frame = 0; + if (!frame) { + if (!(frame = avcodec_alloc_frame())) { + Status=DATA_ERR; + break; + } + } else + avcodec_get_frame_defaults(frame); + + int len = avcodec_decode_audio4(c, frame, &got_frame, &packet); + if (len < 0) { + // skip frame + packet.size = 0; + avcodec_flush_buffers(c); + continue; + } + if (got_frame) { + int out_samples; + outsamples = av_rescale_rnd(swr_get_delay(swr, c->sample_rate) + frame->nb_samples, + c->sample_rate, c->sample_rate, AV_ROUND_UP); + if (outsamples > outsamples_max) { + av_free(outbuf); + if (av_samples_alloc(&outbuf, &out_samples, c->channels, + frame->nb_samples, AV_SAMPLE_FMT_S16, 1) < 0) { + Status=WRITE_ERR; + packet.size = 0; + break; + } + outsamples_max = outsamples; + } + outsamples = swr_convert(swr, &outbuf, outsamples, + (const uint8_t **) &frame->data[0], frame->nb_samples); + int outbuf_size = av_samples_get_buffer_size(&out_samples, c->channels, + outsamples, AV_SAMPLE_FMT_S16, 1); + + if(audioDecoder->WriteClip((unsigned char*) outbuf, outbuf_size) != outbuf_size) + { + fprintf(stderr,"%s: PCM write error (%s).\n", ProgName, strerror(errno)); + Status=WRITE_ERR; + } + } + packet.size -= len; + packet.data += len; + } + if (time_played) + *time_played = time(NULL) - starttime; + } while (*state!=STOP_REQ && Status==OK); + + audioDecoder->StopClip(); + meta_data_valid = false; + + swr_free(&swr); + av_free(outbuf); + av_free_packet(&packet); + avcodec_free_frame(&frame); + avcodec_close(c); + //av_free(avcc); + + DeInit(); + return Status; +} + +bool CFfmpegDec::GetMetaData(FILE *_in, const bool /*nice*/, CAudioMetaData* m) +{ + return SetMetaData(_in, m); +} + +CFfmpegDec* CFfmpegDec::getInstance() +{ + static CFfmpegDec* FfmpegDec = NULL; + if(FfmpegDec == NULL) + { + FfmpegDec = new CFfmpegDec(); + } + return FfmpegDec; +} + +bool CFfmpegDec::SetMetaData(FILE * /* _in */, CAudioMetaData* m) +{ + bool needsInit = (avc == NULL); + if (!meta_data_valid) + { + if (needsInit && !Init()) + return false; + if (0 > avformat_find_stream_info(avc, NULL)) + return false; + + av_dump_format(avc, 0, "", 0); + + codec = NULL; + best_stream = av_find_best_stream(avc, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); + if (best_stream < 0) { + if (needsInit) + DeInit(); + return false; + } + + if (!codec) + codec = avcodec_find_decoder(avc->streams[best_stream]->codec->codec_id); + mSampleRate = avc->streams[best_stream]->codec->sample_rate; + mChannels = av_get_channel_layout_nb_channels(avc->streams[best_stream]->codec->channel_layout); + } + m->samplerate = mSampleRate; + std::stringstream ss; + if (codec) + ss << std::string(codec->long_name) + " / " << mChannels << " channel(s)"; + m->type_info = ss.str(); + m->changed=true; + if (needsInit) + DeInit(); + return true; +} diff --git a/src/driver/audiodec/ffmpegdec.h b/src/driver/audiodec/ffmpegdec.h new file mode 100644 index 000000000..223a52092 --- /dev/null +++ b/src/driver/audiodec/ffmpegdec.h @@ -0,0 +1,71 @@ +/* + Neutrino-GUI - DBoxII-Project + + Copyright (C) 2004 Zwen + Copyright (C) 2013 martii + + ffmpeg audio decoder layer + Homepage: http://forum.tuxbox.org/forum + + License: GPL + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +#ifndef __FFMPEG_DEC__ +#define __FFMPEG_DEC__ + +#include +#include +#include +#include "basedec.h" +# if __WORDSIZE == 64 +# define UINT64_C(c) c ## UL +# else +# define UINT64_C(c) c ## ULL +# endif +extern "C" { +#include +} + +class CFfmpegDec : public CBaseDec +{ +private: + bool meta_data_valid; + int mChannels; + int mSampleRate; + size_t buffer_size; + unsigned char *buffer; + AVFormatContext *avc; + AVCodec *codec; + int best_stream; + void *in; + bool Init(void); + void DeInit(void); + +public: + static CFfmpegDec* getInstance(); + virtual RetCode Decoder(FILE *, int, State*, CAudioMetaData* m, time_t* t, unsigned int* secondsToSkip); + bool GetMetaData(FILE *in, const bool nice, CAudioMetaData* m); + CFfmpegDec(); + ~CFfmpegDec(); + int Read(void *buf, size_t buf_size); + int64_t Seek(int64_t offset, int whence); + +protected: + virtual bool SetMetaData(FILE* in, CAudioMetaData* m); +}; +#endif diff --git a/src/driver/netfile.cpp b/src/driver/netfile.cpp index 03eb62763..0c35bdbcc 100644 --- a/src/driver/netfile.cpp +++ b/src/driver/netfile.cpp @@ -543,7 +543,7 @@ int request_file(URL *url) #define getHeaderVal(a,b) { \ char *_ptr; \ - _ptr = strstr(header, a); \ + _ptr = strcasestr(header, a); \ if(_ptr) \ { \ _ptr = strchr(_ptr, ':'); \ @@ -553,7 +553,7 @@ int request_file(URL *url) #define getHeaderStr(a,b) { \ char *_ptr; \ - _ptr = strstr(header, a); \ + _ptr = strcasestr(header, a); \ if(_ptr) \ { \ unsigned int i; \ @@ -671,6 +671,7 @@ int parse_response(URL *url, void *opt, CSTATE *state) getHeaderStr("icy-url:", state->station_url); getHeaderVal("icy-br:", state->bitrate); } +#if 0 /********************* dirty hack **********************/ /* we parse the stream header sent by the server and */ /* build based on this information an arteficial id3 */ @@ -755,6 +756,7 @@ int parse_response(URL *url, void *opt, CSTATE *state) id3->len = 14 + cnt; } +#endif return meta_interval; }