diff options
Diffstat (limited to 'examples/common/alffmpeg.c')
-rw-r--r-- | examples/common/alffmpeg.c | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/examples/common/alffmpeg.c b/examples/common/alffmpeg.c new file mode 100644 index 00000000..16f73866 --- /dev/null +++ b/examples/common/alffmpeg.c @@ -0,0 +1,638 @@ +/* + * FFmpeg Decoder Helpers + * + * Copyright (c) 2011 by Chris Robinson <[email protected]> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains routines for helping to decode audio using libavformat + * and libavcodec (ffmpeg). There's very little OpenAL-specific code here. */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <assert.h> + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alhelpers.h" +#include "alffmpeg.h" + + +static size_t NextPowerOf2(size_t value) +{ + size_t powerOf2 = 1; + + if(value) + { + value--; + while(value) + { + value >>= 1; + powerOf2 <<= 1; + } + } + return powerOf2; +} + + +struct MemData { + char *buffer; + size_t length; + size_t pos; +}; + +static int MemData_read(void *opaque, uint8_t *buf, int buf_size) +{ + struct MemData *membuf = (struct MemData*)opaque; + int rem = membuf->length - membuf->pos; + + if(rem > buf_size) + rem = buf_size; + + memcpy(buf, &membuf->buffer[membuf->pos], rem); + membuf->pos += rem; + + return rem; +} + +static int MemData_write(void *opaque, uint8_t *buf, int buf_size) +{ + struct MemData *membuf = (struct MemData*)opaque; + int rem = membuf->length - membuf->pos; + + if(rem > buf_size) + rem = buf_size; + + memcpy(&membuf->buffer[membuf->pos], buf, rem); + membuf->pos += rem; + + return rem; +} + +static int64_t MemData_seek(void *opaque, int64_t offset, int whence) +{ + struct MemData *membuf = (struct MemData*)opaque; + + whence &= ~AVSEEK_FORCE; + switch(whence) + { + case SEEK_SET: + if(offset < 0 || (uint64_t)offset > membuf->length) + return -1; + membuf->pos = offset; + break; + + case SEEK_CUR: + if((offset >= 0 && (uint64_t)offset > membuf->length-membuf->pos) || + (offset < 0 && (uint64_t)(-offset) > membuf->pos)) + return -1; + membuf->pos += offset; + break; + + case SEEK_END: + if(offset > 0 || (uint64_t)(-offset) > membuf->length) + return -1; + membuf->pos = membuf->length + offset; + break; + + case AVSEEK_SIZE: + return membuf->length; + + default: + return -1; + } + + return membuf->pos; +} + + +struct PacketList { + AVPacket pkt; + struct PacketList *next; +}; + +struct MyStream { + AVCodecContext *CodecCtx; + int StreamIdx; + + struct PacketList *Packets; + + AVFrame *Frame; + + const uint8_t *FrameData; + size_t FrameDataSize; + + FilePtr parent; +}; + +struct MyFile { + AVFormatContext *FmtCtx; + + StreamPtr *Streams; + size_t StreamsSize; + + struct MemData membuf; +}; + + +static int done_init = 0; + +FilePtr openAVFile(const char *fname) +{ + FilePtr file; + + /* We need to make sure ffmpeg is initialized. Optionally silence warning + * output from the lib */ + if(!done_init) {av_register_all(); + av_log_set_level(AV_LOG_ERROR); + done_init = 1;} + + file = (FilePtr)calloc(1, sizeof(*file)); + if(file && avformat_open_input(&file->FmtCtx, fname, NULL, NULL) == 0) + { + /* After opening, we must search for the stream information because not + * all formats will have it in stream headers */ + if(avformat_find_stream_info(file->FmtCtx, NULL) >= 0) + return file; + avformat_close_input(&file->FmtCtx); + } + + free(file); + return NULL; +} + +FilePtr openAVData(const char *name, char *buffer, size_t buffer_len) +{ + FilePtr file; + + if(!done_init) {av_register_all(); + av_log_set_level(AV_LOG_ERROR); + done_init = 1;} + + if(!name) + name = ""; + + file = (FilePtr)calloc(1, sizeof(*file)); + if(file && (file->FmtCtx=avformat_alloc_context()) != NULL) + { + file->membuf.buffer = buffer; + file->membuf.length = buffer_len; + file->membuf.pos = 0; + + file->FmtCtx->pb = avio_alloc_context(NULL, 0, 0, &file->membuf, + MemData_read, MemData_write, + MemData_seek); + if(file->FmtCtx->pb && avformat_open_input(&file->FmtCtx, name, NULL, NULL) == 0) + { + if(avformat_find_stream_info(file->FmtCtx, NULL) >= 0) + return file; + avformat_close_input(&file->FmtCtx); + } + if(file->FmtCtx) + avformat_free_context(file->FmtCtx); + file->FmtCtx = NULL; + } + + free(file); + return NULL; +} + +FilePtr openAVCustom(const char *name, void *user_data, + int (*read_packet)(void *user_data, uint8_t *buf, int buf_size), + int (*write_packet)(void *user_data, uint8_t *buf, int buf_size), + int64_t (*seek)(void *user_data, int64_t offset, int whence)) +{ + FilePtr file; + + if(!done_init) {av_register_all(); + av_log_set_level(AV_LOG_ERROR); + done_init = 1;} + + if(!name) + name = ""; + + file = (FilePtr)calloc(1, sizeof(*file)); + if(file && (file->FmtCtx=avformat_alloc_context()) != NULL) + { + file->FmtCtx->pb = avio_alloc_context(NULL, 0, 0, user_data, + read_packet, write_packet, seek); + if(file->FmtCtx->pb && avformat_open_input(&file->FmtCtx, name, NULL, NULL) == 0) + { + if(avformat_find_stream_info(file->FmtCtx, NULL) >= 0) + return file; + avformat_close_input(&file->FmtCtx); + } + if(file->FmtCtx) + avformat_free_context(file->FmtCtx); + file->FmtCtx = NULL; + } + + free(file); + return NULL; +} + + +void clearAVAudioData(StreamPtr stream) +{ + while(stream->Packets) + { + struct PacketList *self; + + self = stream->Packets; + stream->Packets = self->next; + + av_free_packet(&self->pkt); + av_free(self); + } +} + + +void closeAVFile(FilePtr file) +{ + size_t i; + + if(!file) return; + + for(i = 0;i < file->StreamsSize;i++) + { + StreamPtr stream = file->Streams[i]; + + while(stream->Packets) + { + struct PacketList *self; + + self = stream->Packets; + stream->Packets = self->next; + + av_free_packet(&self->pkt); + av_free(self); + } + + avcodec_close(stream->CodecCtx); + av_free(stream->Frame); + free(stream); + } + free(file->Streams); + + avformat_close_input(&file->FmtCtx); + free(file); +} + + +int getAVFileInfo(FilePtr file, int *numaudiostreams) +{ + unsigned int i; + int audiocount = 0; + + if(!file) return 1; + for(i = 0;i < file->FmtCtx->nb_streams;i++) + { + if(file->FmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) + audiocount++; + } + *numaudiostreams = audiocount; + return 0; +} + +StreamPtr getAVAudioStream(FilePtr file, int streamnum) +{ + unsigned int i; + if(!file) return NULL; + for(i = 0;i < file->FmtCtx->nb_streams;i++) + { + if(file->FmtCtx->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) + continue; + + if(streamnum == 0) + { + StreamPtr stream; + AVCodec *codec; + void *temp; + size_t j; + + /* Found the requested stream. Check if a handle to this stream + * already exists and return it if it does */ + for(j = 0;j < file->StreamsSize;j++) + { + if(file->Streams[j]->StreamIdx == (int)i) + return file->Streams[j]; + } + + /* Doesn't yet exist. Now allocate a new stream object and fill in + * its info */ + stream = (StreamPtr)calloc(1, sizeof(*stream)); + if(!stream) return NULL; + + stream->parent = file; + stream->CodecCtx = file->FmtCtx->streams[i]->codec; + stream->StreamIdx = i; + + /* Try to find the codec for the given codec ID, and open it */ + codec = avcodec_find_decoder(stream->CodecCtx->codec_id); + if(!codec || avcodec_open2(stream->CodecCtx, codec, NULL) < 0) + { + free(stream); + return NULL; + } + + /* Allocate space for the decoded data to be stored in before it + * gets passed to the app */ + stream->Frame = avcodec_alloc_frame(); + if(!stream->Frame) + { + avcodec_close(stream->CodecCtx); + free(stream); + return NULL; + } + stream->FrameData = NULL; + stream->FrameDataSize = 0; + + /* Append the new stream object to the stream list. The original + * pointer will remain valid if realloc fails, so we need to use + * another pointer to watch for errors and not leak memory */ + temp = realloc(file->Streams, (file->StreamsSize+1) * + sizeof(*file->Streams)); + if(!temp) + { + avcodec_close(stream->CodecCtx); + av_free(stream->Frame); + free(stream); + return NULL; + } + file->Streams = (StreamPtr*)temp; + file->Streams[file->StreamsSize++] = stream; + return stream; + } + streamnum--; + } + return NULL; +} + +int getAVAudioInfo(StreamPtr stream, ALuint *rate, ALenum *channels, ALenum *type) +{ + if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) + return 1; + + /* Get the sample type for OpenAL given the format detected by ffmpeg. */ + if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_U8) + *type = AL_UNSIGNED_BYTE_SOFT; + else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S16) + *type = AL_SHORT_SOFT; + else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S32) + *type = AL_INT_SOFT; + else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT) + *type = AL_FLOAT_SOFT; + else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_DBL) + *type = AL_DOUBLE_SOFT; + else + { + fprintf(stderr, "Unsupported ffmpeg sample format: %s\n", + av_get_sample_fmt_name(stream->CodecCtx->sample_fmt)); + return 1; + } + + /* Get the OpenAL channel configuration using the channel layout detected + * by ffmpeg. NOTE: some file types may not specify a channel layout. In + * that case, one must be guessed based on the channel count. */ + if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + *channels = AL_MONO_SOFT; + else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_STEREO) + *channels = AL_STEREO_SOFT; + else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) + *channels = AL_QUAD_SOFT; + else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + *channels = AL_5POINT1_SOFT; + else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + *channels = AL_7POINT1_SOFT; + else if(stream->CodecCtx->channel_layout == 0) + { + /* Unknown channel layout. Try to guess. */ + if(stream->CodecCtx->channels == 1) + *channels = AL_MONO_SOFT; + else if(stream->CodecCtx->channels == 2) + *channels = AL_STEREO_SOFT; + else + { + fprintf(stderr, "Unsupported ffmpeg raw channel count: %d\n", + stream->CodecCtx->channels); + return 1; + } + } + else + { + char str[1024]; + av_get_channel_layout_string(str, sizeof(str), stream->CodecCtx->channels, + stream->CodecCtx->channel_layout); + fprintf(stderr, "Unsupported ffmpeg channel layout: %s\n", str); + return 1; + } + + *rate = stream->CodecCtx->sample_rate; + + return 0; +} + + +/* Used by getAV*Data to search for more compressed data, and buffer it in the + * correct stream. It won't buffer data for streams that the app doesn't have a + * handle for. */ +static int getNextPacket(FilePtr file, int streamidx) +{ + struct PacketList *packet; + + packet = (struct PacketList*)av_malloc(sizeof(*packet)); + packet->next = NULL; + +next_packet: + while(av_read_frame(file->FmtCtx, &packet->pkt) >= 0) + { + StreamPtr *iter = file->Streams; + StreamPtr *iter_end = iter + file->StreamsSize; + + /* Check each stream the user has a handle for, looking for the one + * this packet belongs to */ + while(iter != iter_end) + { + if((*iter)->StreamIdx == packet->pkt.stream_index) + { + struct PacketList **last; + + last = &(*iter)->Packets; + while(*last != NULL) + last = &(*last)->next; + + *last = packet; + if((*iter)->StreamIdx == streamidx) + return 1; + + packet = (struct PacketList*)av_malloc(sizeof(*packet)); + packet->next = NULL; + goto next_packet; + } + iter++; + } + /* Free the packet and look for another */ + av_free_packet(&packet->pkt); + } + + av_free(packet); + return 0; +} + +uint8_t *getAVAudioData(StreamPtr stream, size_t *length) +{ + int got_frame; + int len; + + if(length) *length = 0; + + if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) + return NULL; + +next_packet: + if(!stream->Packets && !getNextPacket(stream->parent, stream->StreamIdx)) + return NULL; + + /* Decode some data, and check for errors */ + avcodec_get_frame_defaults(stream->Frame); + while((len=avcodec_decode_audio4(stream->CodecCtx, stream->Frame, + &got_frame, &stream->Packets->pkt)) < 0) + { + struct PacketList *self; + + /* Error? Drop it and try the next, I guess... */ + self = stream->Packets; + stream->Packets = self->next; + + av_free_packet(&self->pkt); + av_free(self); + + if(!stream->Packets) + goto next_packet; + } + + if(len < stream->Packets->pkt.size) + { + /* Move the unread data to the front and clear the end bits */ + int remaining = stream->Packets->pkt.size - len; + memmove(stream->Packets->pkt.data, &stream->Packets->pkt.data[len], + remaining); + memset(&stream->Packets->pkt.data[remaining], 0, + stream->Packets->pkt.size - remaining); + stream->Packets->pkt.size -= len; + } + else + { + struct PacketList *self; + + self = stream->Packets; + stream->Packets = self->next; + + av_free_packet(&self->pkt); + av_free(self); + } + + if(!got_frame || stream->Frame->nb_samples == 0) + goto next_packet; + + /* Set the output buffer size */ + *length = av_samples_get_buffer_size(NULL, stream->CodecCtx->channels, + stream->Frame->nb_samples, + stream->CodecCtx->sample_fmt, 1); + + return stream->Frame->data[0]; +} + +size_t readAVAudioData(StreamPtr stream, void *data, size_t length) +{ + size_t dec = 0; + + if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) + return 0; + + while(dec < length) + { + /* If there's no decoded data, find some */ + if(stream->FrameDataSize == 0) + { + stream->FrameData = getAVAudioData(stream, &stream->FrameDataSize); + if(!stream->FrameData) + break; + } + + if(stream->FrameDataSize > 0) + { + /* Get the amount of bytes remaining to be written, and clamp to + * the amount of decoded data we have */ + size_t rem = length-dec; + if(rem > stream->FrameDataSize) + rem = stream->FrameDataSize; + + /* Copy the data to the app's buffer and increment */ + if(data != NULL) + { + memcpy(data, stream->FrameData, rem); + data = (char*)data + rem; + } + dec += rem; + + /* If there's any decoded data left, move it to the front of the + * buffer for next time */ + stream->FrameData += rem; + stream->FrameDataSize -= rem; + } + } + + /* Return the number of bytes we were able to get */ + return dec; +} + +void *decodeAVAudioStream(StreamPtr stream, size_t *length) +{ + char *outbuf = NULL; + size_t buflen = 0; + void *inbuf; + size_t got; + + *length = 0; + if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) + return NULL; + + while((inbuf=getAVAudioData(stream, &got)) != NULL && got > 0) + { + void *ptr; + + ptr = realloc(outbuf, NextPowerOf2(buflen+got)); + if(ptr == NULL) + break; + outbuf = (char*)ptr; + + memcpy(&outbuf[buflen], inbuf, got); + buflen += got; + } + outbuf = (char*)realloc(outbuf, buflen); + + *length = buflen; + return outbuf; +} |