| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- /*
- * 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;
- }
|