diff options
author | Joerg Mueller <nexyon@gmail.com> | 2009-08-10 01:16:39 +0400 |
---|---|---|
committer | Joerg Mueller <nexyon@gmail.com> | 2009-08-10 01:16:39 +0400 |
commit | 6c5c58e05799f2b593cd81fcff57e6ef72ad57fb (patch) | |
tree | 8add929ef94d03fc69aecce6ef2baf283505782f /intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp | |
parent | c1ca2ab5dceb8d5355215a3c7a80b171f394e487 (diff) |
2.5: Sound branch merge!
See mailing list for additional information.
Diffstat (limited to 'intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp')
-rw-r--r-- | intern/audaspace/ffmpeg/AUD_FFMPEGReader.cpp | 388 |
1 files changed, 388 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>. + * + * ***** END LGPL LICENSE BLOCK ***** + */ + +// needed for INT64_C +#define __STDC_CONSTANT_MACROS + +#include "AUD_FFMPEGReader.h" +#include "AUD_Buffer.h" + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +} + +// 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; +} |