/* * FFmpeg Decoder Helpers * * Copyright (c) 2011 by Chris Robinson * * 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 #include #include #include #include #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 || offset > membuf->length) return -1; membuf->pos = offset; break; case SEEK_CUR: if((offset >= 0 && offset > membuf->length-membuf->pos) || (offset < 0 && offset < -membuf->pos)) return -1; membuf->pos += offset; break; case SEEK_END: if(offset > 0 || 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; char *DecodedData; size_t DecodedDataSize; 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; av_close_input_file(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; av_close_input_file(file->FmtCtx); file->FmtCtx = NULL; } 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; av_close_input_file(file->FmtCtx); file->FmtCtx = NULL; } if(file->FmtCtx) avformat_free_context(file->FmtCtx); file->FmtCtx = NULL; } free(file); return NULL; } 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->DecodedData); free(stream); } free(file->Streams); av_close_input_file(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_open(stream->CodecCtx, codec) < 0) { free(stream); return NULL; } /* Allocate space for the decoded data to be stored in before it * gets passed to the app */ stream->DecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); if(!stream->DecodedData) { avcodec_close(stream->CodecCtx); free(stream); return NULL; } /* 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->DecodedData); 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; } void *getAVAudioData(StreamPtr stream, size_t *length) { int size; int len; if(length) *length = 0; if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) return NULL; stream->DecodedDataSize = 0; next_packet: if(!stream->Packets && !getNextPacket(stream->parent, stream->StreamIdx)) return NULL; /* Decode some data, and check for errors */ size = AVCODEC_MAX_AUDIO_FRAME_SIZE; while((len=avcodec_decode_audio3(stream->CodecCtx, (int16_t*)stream->DecodedData, &size, &stream->Packets->pkt)) == 0) { struct PacketList *self; if(size > 0) break; /* Packet went unread and no data was given? 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; size = AVCODEC_MAX_AUDIO_FRAME_SIZE; } if(len < 0) return NULL; 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(size == 0) goto next_packet; /* Set the output buffer size */ stream->DecodedDataSize = size; if(length) *length = stream->DecodedDataSize; return stream->DecodedData; } 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->DecodedDataSize == 0) { if(getAVAudioData(stream, NULL) == NULL) break; } if(stream->DecodedDataSize > 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->DecodedDataSize) rem = stream->DecodedDataSize; /* Copy the data to the app's buffer and increment */ if(data != NULL) { memcpy(data, stream->DecodedData, 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 */ if(rem < stream->DecodedDataSize) memmove(stream->DecodedData, &stream->DecodedData[rem], stream->DecodedDataSize - rem); stream->DecodedDataSize -= 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; }