From 6c5c58e05799f2b593cd81fcff57e6ef72ad57fb Mon Sep 17 00:00:00 2001 From: Joerg Mueller Date: Sun, 9 Aug 2009 21:16:39 +0000 Subject: 2.5: Sound branch merge! See mailing list for additional information. --- intern/audaspace/ffmpeg/AUD_FFMPEGFactory.cpp | 88 ++++++ intern/audaspace/ffmpeg/AUD_FFMPEGFactory.h | 76 +++++ intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp | 388 ++++++++++++++++++++++++++ intern/audaspace/ffmpeg/AUD_FFMPEGReader.h | 133 +++++++++ intern/audaspace/ffmpeg/Makefile | 41 +++ 5 files changed, 726 insertions(+) create mode 100644 intern/audaspace/ffmpeg/AUD_FFMPEGFactory.cpp create mode 100644 intern/audaspace/ffmpeg/AUD_FFMPEGFactory.h create mode 100644 intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp create mode 100644 intern/audaspace/ffmpeg/AUD_FFMPEGReader.h create mode 100644 intern/audaspace/ffmpeg/Makefile (limited to 'intern/audaspace/ffmpeg') diff --git a/intern/audaspace/ffmpeg/AUD_FFMPEGFactory.cpp b/intern/audaspace/ffmpeg/AUD_FFMPEGFactory.cpp new file mode 100644 index 00000000000..5f9006b0ec0 --- /dev/null +++ b/intern/audaspace/ffmpeg/AUD_FFMPEGFactory.cpp @@ -0,0 +1,88 @@ +/* + * $Id$ + * + * ***** BEGIN LGPL LICENSE BLOCK ***** + * + * Copyright 2009 Jörg Hermann Müller + * + * This file is part of AudaSpace. + * + * AudaSpace 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 3 of the License, or + * (at your option) any later version. + * + * AudaSpace 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 AudaSpace. If not, see . + * + * ***** END LGPL LICENSE BLOCK ***** + */ + +#include "AUD_FFMPEGFactory.h" +#include "AUD_FFMPEGReader.h" +#include "AUD_Space.h" + +extern "C" { +#include +} + +AUD_FFMPEGFactory::AUD_FFMPEGFactory(const char* filename) +{ + if(filename != 0) + { + m_filename = new char[strlen(filename)+1]; AUD_NEW("string") + strcpy(m_filename, filename); + } + else + m_filename = 0; + m_buffer = 0; + m_size = 0; +} + +AUD_FFMPEGFactory::AUD_FFMPEGFactory(unsigned char* buffer, int size) +{ + m_filename = 0; + m_buffer = (unsigned char*)av_malloc(size); AUD_NEW("buffer") + m_size = size; + memcpy(m_buffer, buffer, size); +} + +AUD_FFMPEGFactory::~AUD_FFMPEGFactory() +{ + if(m_filename) + { + delete[] m_filename; AUD_DELETE("string") + } + if(m_buffer) + { + av_free(m_buffer); AUD_DELETE("buffer") + } +} + +AUD_IReader* AUD_FFMPEGFactory::createReader() +{ + try + { + AUD_IReader* reader; + if(m_filename) + reader = new AUD_FFMPEGReader(m_filename); + else + reader = new AUD_FFMPEGReader(m_buffer, m_size); + AUD_NEW("reader") + return reader; + } + catch(AUD_Exception e) + { + // return 0 if ffmpeg cannot read the file + if(e.error == AUD_ERROR_FFMPEG) + return 0; + // but throw an exception if the file doesn't exist + else + throw; + } +} diff --git a/intern/audaspace/ffmpeg/AUD_FFMPEGFactory.h b/intern/audaspace/ffmpeg/AUD_FFMPEGFactory.h new file mode 100644 index 00000000000..0a9fcc22c8b --- /dev/null +++ b/intern/audaspace/ffmpeg/AUD_FFMPEGFactory.h @@ -0,0 +1,76 @@ +/* + * $Id$ + * + * ***** BEGIN LGPL LICENSE BLOCK ***** + * + * Copyright 2009 Jörg Hermann Müller + * + * This file is part of AudaSpace. + * + * AudaSpace 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 3 of the License, or + * (at your option) any later version. + * + * AudaSpace 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 AudaSpace. If not, see . + * + * ***** END LGPL LICENSE BLOCK ***** + */ + +#ifndef AUD_FFMPEGFACTORY +#define AUD_FFMPEGFACTORY + +#include "AUD_IFactory.h" + +/** + * This factory reads a sound file via ffmpeg. + * \warning Notice that the needed formats and codecs have to be registered + * for ffmpeg before this class can be used. + */ +class AUD_FFMPEGFactory : public AUD_IFactory +{ +private: + /** + * The filename of the sound source file. + */ + char* m_filename; + + /** + * The buffer to read from. + */ + unsigned char* m_buffer; + + /** + * The size of the buffer. + */ + int m_size; + +public: + /** + * Creates a new factory. + * \param filename The sound file path. + */ + AUD_FFMPEGFactory(const char* filename); + + /** + * Creates a new factory. + * \param buffer The buffer to read from. + * \param size The size of the buffer. + */ + AUD_FFMPEGFactory(unsigned char* buffer, int size); + + /** + * Destroys the factory. + */ + ~AUD_FFMPEGFactory(); + + virtual AUD_IReader* createReader(); +}; + +#endif //AUD_FFMPEGFACTORY diff --git a/intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp b/intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp new file mode 100644 index 00000000000..28356e3b97d --- /dev/null +++ b/intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp @@ -0,0 +1,388 @@ +/* + * $Id$ + * + * ***** BEGIN LGPL LICENSE BLOCK ***** + * + * Copyright 2009 Jörg Hermann Müller + * + * This file is part of AudaSpace. + * + * AudaSpace 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 3 of the License, or + * (at your option) any later version. + * + * AudaSpace 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 AudaSpace. If not, see . + * + * ***** END LGPL LICENSE BLOCK ***** + */ + +// needed for INT64_C +#define __STDC_CONSTANT_MACROS + +#include "AUD_FFMPEGReader.h" +#include "AUD_Buffer.h" + +extern "C" { +#include +#include +} + +// This function transforms a FFMPEG SampleFormat to or own sample format +static inline AUD_SampleFormat FFMPEG_TO_AUD(SampleFormat fmt) +{ + switch(fmt) + { + case SAMPLE_FMT_U8: + return AUD_FORMAT_U8; + case SAMPLE_FMT_S16: + return AUD_FORMAT_S16; + case SAMPLE_FMT_S32: + return AUD_FORMAT_S32; + case SAMPLE_FMT_FLT: + return AUD_FORMAT_FLOAT32; + case SAMPLE_FMT_DBL: + return AUD_FORMAT_FLOAT64; + default: + return AUD_FORMAT_INVALID; + } +} + +int AUD_FFMPEGReader::decode(AVPacket* packet, AUD_Buffer* buffer) +{ + // save packet parameters + uint8_t *audio_pkg_data = packet->data; + int audio_pkg_size = packet->size; + + int buf_size = buffer->getSize(); + int buf_pos = 0; + + int read_length, data_size; + + // as long as there is still data in the package + while(audio_pkg_size > 0) + { + // resize buffer if needed + if(buf_size - buf_pos < AVCODEC_MAX_AUDIO_FRAME_SIZE) + { + buffer->resize(buf_size + AVCODEC_MAX_AUDIO_FRAME_SIZE, true); + buf_size += AVCODEC_MAX_AUDIO_FRAME_SIZE; + } + + // read samples from the packet + data_size = buf_size - buf_pos; + /*read_length = avcodec_decode_audio3(m_codecCtx, + (int16_t*)(buffer->getBuffer()+buf_pos), + &data_size, + packet);*/ + read_length = avcodec_decode_audio2(m_codecCtx, + (int16_t*)(buffer->getBuffer()+buf_pos), + &data_size, + audio_pkg_data, + audio_pkg_size); + + buf_pos += data_size; + + // read error, next packet! + if(read_length < 0) + break; + + // move packet parameters + audio_pkg_data += read_length; + audio_pkg_size -= read_length; + } + + return buf_pos; +} + +AUD_FFMPEGReader::AUD_FFMPEGReader(const char* filename) +{ + m_position = 0; + m_pkgbuf_left = 0; + m_byteiocontext = NULL; + + // open file + if(av_open_input_file(&m_formatCtx, filename, NULL, 0, NULL)!=0) + AUD_THROW(AUD_ERROR_FILE); + + try + { + if(av_find_stream_info(m_formatCtx)<0) + AUD_THROW(AUD_ERROR_FFMPEG); + + // find audio stream and codec + m_stream = -1; + + for(int i = 0; i < m_formatCtx->nb_streams; i++) + if((m_formatCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) + && (m_stream < 0)) + { + m_stream=i; + break; + } + if(m_stream == -1) + AUD_THROW(AUD_ERROR_FFMPEG); + + m_codecCtx = m_formatCtx->streams[m_stream]->codec; + + // get a decoder and open it + AVCodec *aCodec = avcodec_find_decoder(m_codecCtx->codec_id); + if(!aCodec) + AUD_THROW(AUD_ERROR_FFMPEG); + + if(avcodec_open(m_codecCtx, aCodec)<0) + AUD_THROW(AUD_ERROR_FFMPEG); + + // XXX this prints file information to stdout: + //dump_format(m_formatCtx, 0, filename, 0); + + m_specs.channels = (AUD_Channels) m_codecCtx->channels; + m_specs.format = FFMPEG_TO_AUD(m_codecCtx->sample_fmt); + m_specs.rate = (AUD_SampleRate) m_codecCtx->sample_rate; + } + catch(AUD_Exception e) + { + av_close_input_file(m_formatCtx); + throw; + } + + // last but not least if there hasn't been any error, create the buffers + m_buffer = new AUD_Buffer(); AUD_NEW("buffer") + m_pkgbuf = new AUD_Buffer(AVCODEC_MAX_AUDIO_FRAME_SIZE<<1); + AUD_NEW("buffer") +} + +AUD_FFMPEGReader::AUD_FFMPEGReader(unsigned char* buffer, int size) +{ + m_position = 0; + m_pkgbuf_left = 0; + m_byteiocontext = (ByteIOContext*)av_mallocz(sizeof(ByteIOContext)); + AUD_NEW("byteiocontext") + + if(init_put_byte(m_byteiocontext, buffer, size, 0, + NULL, NULL, NULL, NULL) != 0) + AUD_THROW(AUD_ERROR_FILE); + + AVProbeData probe_data; + probe_data.filename = ""; + probe_data.buf = buffer; + probe_data.buf_size = size; + AVInputFormat* fmt = av_probe_input_format(&probe_data, 1); + + // open stream + if(av_open_input_stream(&m_formatCtx, m_byteiocontext, "", fmt, NULL)!=0) + AUD_THROW(AUD_ERROR_FILE); + + try + { + if(av_find_stream_info(m_formatCtx)<0) + AUD_THROW(AUD_ERROR_FFMPEG); + + // find audio stream and codec + m_stream = -1; + + for(int i = 0; i < m_formatCtx->nb_streams; i++) + if((m_formatCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) + && (m_stream < 0)) + { + m_stream=i; + break; + } + if(m_stream == -1) + AUD_THROW(AUD_ERROR_FFMPEG); + + m_codecCtx = m_formatCtx->streams[m_stream]->codec; + + // get a decoder and open it + AVCodec *aCodec = avcodec_find_decoder(m_codecCtx->codec_id); + if(!aCodec) + AUD_THROW(AUD_ERROR_FFMPEG); + + if(avcodec_open(m_codecCtx, aCodec)<0) + AUD_THROW(AUD_ERROR_FFMPEG); + + // XXX this prints stream information to stdout: + //dump_format(m_formatCtx, 0, NULL, 0); + + m_specs.channels = (AUD_Channels) m_codecCtx->channels; + m_specs.format = FFMPEG_TO_AUD(m_codecCtx->sample_fmt); + m_specs.rate = (AUD_SampleRate) m_codecCtx->sample_rate; + } + catch(AUD_Exception e) + { + av_close_input_stream(m_formatCtx); + av_free(m_byteiocontext); AUD_DELETE("byteiocontext") + throw; + } + + // last but not least if there hasn't been any error, create the buffers + m_buffer = new AUD_Buffer(); AUD_NEW("buffer") + m_pkgbuf = new AUD_Buffer(AVCODEC_MAX_AUDIO_FRAME_SIZE<<1); + AUD_NEW("buffer") +} + +AUD_FFMPEGReader::~AUD_FFMPEGReader() +{ + avcodec_close(m_codecCtx); + + if(m_byteiocontext) + { + av_close_input_stream(m_formatCtx); + av_free(m_byteiocontext); AUD_DELETE("byteiocontext") + } + else + av_close_input_file(m_formatCtx); + + delete m_buffer; AUD_DELETE("buffer") + delete m_pkgbuf; AUD_DELETE("buffer") +} + +bool AUD_FFMPEGReader::isSeekable() +{ + return true; +} + +void AUD_FFMPEGReader::seek(int position) +{ + if(position >= 0) + { + // a value < 0 tells us that seeking failed + if(av_seek_frame(m_formatCtx, + -1, + (uint64_t)(((uint64_t)position * + (uint64_t)AV_TIME_BASE) / + (uint64_t)m_specs.rate), + AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY) >= 0) + { + avcodec_flush_buffers(m_codecCtx); + m_position = position; + + AVPacket packet; + bool search = true; + + while(search && av_read_frame(m_formatCtx, &packet) >= 0) + { + // is it a frame from the audio stream? + if(packet.stream_index == m_stream) + { + // decode the package + m_pkgbuf_left = decode(&packet, m_pkgbuf); + search = false; + + // check position + if(packet.pts != AV_NOPTS_VALUE) + { + // calculate real position, and read to frame! + m_position = packet.pts * + av_q2d(m_formatCtx->streams[m_stream]->time_base) * + m_specs.rate; + + if(m_position < position) + { + sample_t* buf; + int length = position - m_position; + read(length, buf); + } + } + } + av_free_packet(&packet); + } + } + else + { + // Seeking failed, do nothing. + } + } +} + +int AUD_FFMPEGReader::getLength() +{ + // return approximated remaning size + return (int)((m_formatCtx->duration * m_codecCtx->sample_rate) + / AV_TIME_BASE)-m_position; +} + +int AUD_FFMPEGReader::getPosition() +{ + return m_position; +} + +AUD_Specs AUD_FFMPEGReader::getSpecs() +{ + return m_specs; +} + +AUD_ReaderType AUD_FFMPEGReader::getType() +{ + return AUD_TYPE_STREAM; +} + +bool AUD_FFMPEGReader::notify(AUD_Message &message) +{ + return false; +} + +void AUD_FFMPEGReader::read(int & length, sample_t* & buffer) +{ + // read packages and decode them + AVPacket packet; + int data_size = 0; + int pkgbuf_size = m_pkgbuf->getSize(); + int pkgbuf_pos; + int left = length; + int sample_size = AUD_SAMPLE_SIZE(m_specs); + + // resize output buffer if necessary + if(m_buffer->getSize() < length*sample_size) + m_buffer->resize(length*sample_size); + + buffer = m_buffer->getBuffer(); + pkgbuf_pos = m_pkgbuf_left; + m_pkgbuf_left = 0; + + // there may still be data in the buffer from the last call + if(pkgbuf_pos > 0) + { + data_size = AUD_MIN(pkgbuf_pos, left * sample_size); + memcpy(buffer, m_pkgbuf->getBuffer(), data_size); + buffer += data_size; + left -= data_size/sample_size; + } + + // for each frame read as long as there isn't enough data already + while((left > 0) && (av_read_frame(m_formatCtx, &packet) >= 0)) + { + // is it a frame from the audio stream? + if(packet.stream_index == m_stream) + { + // decode the package + pkgbuf_pos = decode(&packet, m_pkgbuf); + + // copy to output buffer + data_size = AUD_MIN(pkgbuf_pos, left * sample_size); + memcpy(buffer, m_pkgbuf->getBuffer(), data_size); + buffer += data_size; + left -= data_size/sample_size; + } + av_free_packet(&packet); + } + // read more data than necessary? + if(pkgbuf_pos > data_size) + { + m_pkgbuf_left = pkgbuf_pos-data_size; + memmove(m_pkgbuf->getBuffer(), m_pkgbuf->getBuffer()+data_size, + pkgbuf_pos-data_size); + } + + buffer = m_buffer->getBuffer(); + + if(left > 0) + length -= left; + m_position += length; +} diff --git a/intern/audaspace/ffmpeg/AUD_FFMPEGReader.h b/intern/audaspace/ffmpeg/AUD_FFMPEGReader.h new file mode 100644 index 00000000000..645f5f356f0 --- /dev/null +++ b/intern/audaspace/ffmpeg/AUD_FFMPEGReader.h @@ -0,0 +1,133 @@ +/* + * $Id$ + * + * ***** BEGIN LGPL LICENSE BLOCK ***** + * + * Copyright 2009 Jörg Hermann Müller + * + * This file is part of AudaSpace. + * + * AudaSpace 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 3 of the License, or + * (at your option) any later version. + * + * AudaSpace 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 AudaSpace. If not, see . + * + * ***** END LGPL LICENSE BLOCK ***** + */ + +#ifndef AUD_FFMPEGREADER +#define AUD_FFMPEGREADER + +#include "AUD_IReader.h" +class AUD_Buffer; +struct AVCodecContext; +extern "C" { +#include +} + +/** + * This class reads a sound file via ffmpeg. + * \warning Seeking may not be accurate! Moreover the position is updated after + * a buffer reading call. So calling getPosition right after seek + * normally results in a wrong value. + * \warning Playback of an ogg with some outdated ffmpeg versions results in a + * segfault on windows. + */ +class AUD_FFMPEGReader : public AUD_IReader +{ +private: + /** + * The current position in samples. + */ + int m_position; + + /** + * The playback buffer. + */ + AUD_Buffer *m_buffer; + + /** + * The specification of the audio data. + */ + AUD_Specs m_specs; + + /** + * The buffer for package reading. + */ + AUD_Buffer *m_pkgbuf; + + /** + * The count of samples still available from the last read package. + */ + int m_pkgbuf_left; + + /** + * The AVFormatContext structure for using ffmpeg. + */ + AVFormatContext* m_formatCtx; + + /** + * The AVCodecContext structure for using ffmpeg. + */ + AVCodecContext* m_codecCtx; + + /** + * The ByteIOContext to read the data from. + */ + ByteIOContext* m_byteiocontext; + + /** + * The stream ID in the file. + */ + int m_stream; + + /** + * Decodes a packet into the given buffer. + * \param packet The AVPacket to decode. + * \param buffer The target buffer. + * \return The count of read bytes. + */ + int decode(AVPacket* packet, AUD_Buffer* buffer); + +public: + /** + * Creates a new reader. + * \param filename The path to the file to be read. + * \exception AUD_Exception Thrown if the file specified does not exist or + * cannot be read with ffmpeg. + */ + AUD_FFMPEGReader(const char* filename); + + /** + * Creates a new reader. + * \param buffer The buffer to read from. + * \param size The size of the buffer. + * \exception AUD_Exception Thrown if the buffer specified cannot be read + * with ffmpeg. + */ + AUD_FFMPEGReader(unsigned char* buffer, int size); + + /** + * Destroys the reader and closes the file. + */ + virtual ~AUD_FFMPEGReader(); + + virtual bool isSeekable(); + virtual void seek(int position); + virtual int getLength(); + virtual int getPosition(); + virtual AUD_Specs getSpecs(); + virtual AUD_ReaderType getType(); + virtual bool notify(AUD_Message &message); + virtual void read(int & length, sample_t* & buffer); +}; + +#endif //AUD_FFMPEGREADER diff --git a/intern/audaspace/ffmpeg/Makefile b/intern/audaspace/ffmpeg/Makefile new file mode 100644 index 00000000000..c108b020212 --- /dev/null +++ b/intern/audaspace/ffmpeg/Makefile @@ -0,0 +1,41 @@ +# +# $Id: Makefile 13161 2008-01-07 19:13:47Z hos $ +# +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# 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-1307, USA. +# +# The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. +# All rights reserved. +# +# The Original Code is: all of this file. +# +# Contributor(s): none yet. +# +# ***** END GPL LICENSE BLOCK ***** +# +# + +LIBNAME = ffmpegaudaspace +DIR = $(OCGDIR)/intern/$(LIBNAME) + +include nan_compile.mk + +CCFLAGS += $(LEVEL_1_CPP_WARNINGS) + +CPPFLAGS += $(NAN_FFMPEGCFLAGS) +CPPFLAGS += -I../intern +CPPFLAGS += -I.. +CPPFLAGS += -I. -- cgit v1.2.3