2008-02-26 20:53:18 +00:00
|
|
|
//
|
2013-10-07 17:33:35 +00:00
|
|
|
// FFMPEGDecoder.m
|
|
|
|
// FFMPEG
|
2008-02-26 20:53:18 +00:00
|
|
|
//
|
|
|
|
// Created by Andre Reffhaug on 2/26/08.
|
|
|
|
// Copyright 2008 __MyCompanyName__. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
// test
|
2013-10-07 17:33:35 +00:00
|
|
|
#import "FFMPEGDecoder.h"
|
|
|
|
#import "FFMPEGFileProtocols.h"
|
2008-02-26 20:53:18 +00:00
|
|
|
|
2013-10-02 21:58:18 +00:00
|
|
|
#include <pthread.h>
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
|
|
|
|
2008-02-28 05:33:56 +00:00
|
|
|
#define ST_BUFF 2048
|
2008-02-26 20:53:18 +00:00
|
|
|
|
2013-10-07 17:33:35 +00:00
|
|
|
@implementation FFMPEGDecoder
|
2008-02-26 20:53:18 +00:00
|
|
|
|
2008-02-28 05:33:56 +00:00
|
|
|
|
2013-10-02 21:58:18 +00:00
|
|
|
int lockmgr_callback(void ** mutex, enum AVLockOp op)
|
|
|
|
{
|
|
|
|
switch (op)
|
|
|
|
{
|
|
|
|
case AV_LOCK_CREATE:
|
|
|
|
*mutex = malloc(sizeof(pthread_mutex_t));
|
|
|
|
pthread_mutex_init(*mutex, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AV_LOCK_DESTROY:
|
|
|
|
pthread_mutex_destroy(*mutex);
|
|
|
|
free(*mutex);
|
|
|
|
*mutex = NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AV_LOCK_OBTAIN:
|
|
|
|
pthread_mutex_lock(*mutex);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AV_LOCK_RELEASE:
|
|
|
|
pthread_mutex_unlock(*mutex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2008-02-28 05:33:56 +00:00
|
|
|
|
2013-10-02 21:58:18 +00:00
|
|
|
+ (void)initialize
|
|
|
|
{
|
2013-10-11 03:55:32 +00:00
|
|
|
if(self == [FFMPEGDecoder class])
|
|
|
|
{
|
|
|
|
av_log_set_flags(AV_LOG_SKIP_REPEATED);
|
|
|
|
av_log_set_level(AV_LOG_ERROR);
|
|
|
|
av_register_all();
|
|
|
|
registerCogProtocols();
|
|
|
|
av_lockmgr_register(lockmgr_callback);
|
|
|
|
}
|
2013-10-02 21:58:18 +00:00
|
|
|
}
|
2008-02-28 05:33:56 +00:00
|
|
|
|
|
|
|
- (BOOL)open:(id<CogSource>)s
|
|
|
|
{
|
2013-10-11 03:55:32 +00:00
|
|
|
int errcode, i;
|
|
|
|
const char *filename = [[[[s url] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] UTF8String];
|
2008-02-28 13:11:37 +00:00
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
formatCtx = NULL;
|
|
|
|
totalFrames = 0;
|
|
|
|
framesRead = 0;
|
|
|
|
|
2008-02-28 12:49:19 +00:00
|
|
|
// register all available codecs
|
2008-02-28 05:33:56 +00:00
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
if ((errcode = avformat_open_input(&formatCtx, filename, NULL, NULL)) < 0)
|
2008-03-01 14:11:30 +00:00
|
|
|
{
|
2013-10-11 03:55:32 +00:00
|
|
|
char errDescr[4096];
|
|
|
|
av_strerror(errcode, errDescr, 4096);
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"ERROR OPENING FILE, errcode = %d, error = %s", errcode, errDescr);
|
2008-03-01 14:11:30 +00:00
|
|
|
return NO;
|
|
|
|
}
|
2008-02-28 05:33:56 +00:00
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
if(avformat_find_stream_info(formatCtx, NULL) < 0)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"CAN'T FIND STREAM INFO!");
|
2013-10-11 03:55:32 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2013-10-03 10:44:39 +00:00
|
|
|
streamIndex = -1;
|
2013-10-11 03:55:32 +00:00
|
|
|
for(i = 0; i < formatCtx->nb_streams; i++) {
|
|
|
|
codecCtx = formatCtx->streams[i]->codec;
|
|
|
|
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO)
|
2008-02-28 05:33:56 +00:00
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"audio codec found");
|
2013-10-03 10:44:39 +00:00
|
|
|
streamIndex = i;
|
2008-02-28 05:33:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-10-03 10:44:39 +00:00
|
|
|
|
|
|
|
if ( streamIndex < 0 ) {
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"no audio codec found");
|
2013-10-03 10:44:39 +00:00
|
|
|
return NO;
|
|
|
|
}
|
2008-02-28 05:33:56 +00:00
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
AVCodec * codec = avcodec_find_decoder(codecCtx->codec_id);
|
2008-02-28 05:33:56 +00:00
|
|
|
if (!codec) {
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"codec not found");
|
2008-02-28 05:33:56 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"could not open codec");
|
2008-02-28 05:33:56 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
lastDecodedFrame = avcodec_alloc_frame();
|
|
|
|
avcodec_get_frame_defaults(lastDecodedFrame);
|
|
|
|
lastReadPacket = malloc(sizeof(AVPacket));
|
|
|
|
av_new_packet(lastReadPacket, 0);
|
|
|
|
readNextPacket = YES;
|
2013-10-11 04:52:32 +00:00
|
|
|
bytesConsumedFromDecodedFrame = INT_MAX;
|
2013-10-11 03:55:32 +00:00
|
|
|
|
|
|
|
frequency = codecCtx->sample_rate;
|
|
|
|
channels = codecCtx->channels;
|
2013-10-05 21:15:09 +00:00
|
|
|
floatingPoint = NO;
|
2013-10-11 03:55:32 +00:00
|
|
|
|
|
|
|
switch (codecCtx->sample_fmt) {
|
2013-10-02 21:58:18 +00:00
|
|
|
case AV_SAMPLE_FMT_U8:
|
|
|
|
case AV_SAMPLE_FMT_U8P:
|
|
|
|
bitsPerSample = 8;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AV_SAMPLE_FMT_S16:
|
|
|
|
case AV_SAMPLE_FMT_S16P:
|
|
|
|
bitsPerSample = 16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AV_SAMPLE_FMT_S32:
|
|
|
|
case AV_SAMPLE_FMT_S32P:
|
2013-10-05 21:15:09 +00:00
|
|
|
bitsPerSample = 32;
|
|
|
|
break;
|
|
|
|
|
2013-10-02 21:58:18 +00:00
|
|
|
case AV_SAMPLE_FMT_FLT:
|
|
|
|
case AV_SAMPLE_FMT_FLTP:
|
2013-10-05 21:15:09 +00:00
|
|
|
bitsPerSample = 32;
|
|
|
|
floatingPoint = YES;
|
|
|
|
break;
|
|
|
|
|
2013-10-02 21:58:18 +00:00
|
|
|
case AV_SAMPLE_FMT_DBL:
|
|
|
|
case AV_SAMPLE_FMT_DBLP:
|
2013-10-05 21:15:09 +00:00
|
|
|
bitsPerSample = 64;
|
|
|
|
floatingPoint = YES;
|
2013-10-02 21:58:18 +00:00
|
|
|
break;
|
2013-10-03 08:00:58 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
return NO;
|
2013-10-02 21:58:18 +00:00
|
|
|
}
|
2013-10-11 03:55:32 +00:00
|
|
|
|
|
|
|
totalFrames = codecCtx->sample_rate * ((float)formatCtx->duration/AV_TIME_BASE);
|
|
|
|
bitrate = (codecCtx->bit_rate) / 1000;
|
|
|
|
framesRead = 0;
|
|
|
|
|
|
|
|
seekable = [s seekable];
|
2008-02-28 12:49:19 +00:00
|
|
|
|
2008-02-28 05:33:56 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)close
|
|
|
|
{
|
2013-10-11 03:55:32 +00:00
|
|
|
if (lastReadPacket)
|
|
|
|
{
|
|
|
|
av_free_packet(lastReadPacket);
|
|
|
|
free(lastReadPacket);
|
|
|
|
lastReadPacket = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastDecodedFrame) { av_free(lastDecodedFrame); lastDecodedFrame = NULL; }
|
|
|
|
|
|
|
|
if (codecCtx) { avcodec_close(codecCtx); codecCtx = NULL; }
|
|
|
|
|
|
|
|
if (formatCtx) { avformat_close_input(&(formatCtx)); formatCtx = NULL; }
|
2008-02-28 05:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
|
|
|
{
|
2013-10-11 03:55:32 +00:00
|
|
|
int frameSize = channels * (bitsPerSample / 8);
|
|
|
|
int gotFrame = 0;
|
|
|
|
int dataSize = 0;
|
|
|
|
|
|
|
|
int bytesToRead = frames * frameSize;
|
|
|
|
int bytesRead = 0;
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
BOOL endOfStream = NO;
|
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
int8_t* targetBuf = (int8_t*) buf;
|
|
|
|
memset(buf, 0, bytesToRead);
|
|
|
|
|
|
|
|
while (bytesRead < bytesToRead)
|
|
|
|
{
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
// buffer size needed to hold decoded samples, in bytes
|
|
|
|
int planeSize;
|
|
|
|
int planar = av_sample_fmt_is_planar(codecCtx->sample_fmt);
|
|
|
|
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels,
|
|
|
|
lastDecodedFrame->nb_samples,
|
|
|
|
codecCtx->sample_fmt, 1);
|
|
|
|
|
|
|
|
if(readNextPacket && !endOfStream)
|
2013-10-11 03:55:32 +00:00
|
|
|
{
|
|
|
|
// consume next chunk of encoded data from input stream
|
|
|
|
av_free_packet(lastReadPacket);
|
|
|
|
if(av_read_frame(formatCtx, lastReadPacket) < 0)
|
2013-10-09 06:57:58 +00:00
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"End of stream");
|
|
|
|
endOfStream = YES;
|
|
|
|
if (dataSize <= bytesConsumedFromDecodedFrame)
|
|
|
|
break; // end of stream;
|
2013-10-09 06:57:58 +00:00
|
|
|
}
|
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
readNextPacket = NO; // we probably won't need to consume another chunk
|
|
|
|
bytesReadFromPacket = 0; // until this one is fully decoded
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dataSize <= bytesConsumedFromDecodedFrame)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
if (endOfStream)
|
|
|
|
break;
|
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
// consumed all decoded samples - decode more
|
|
|
|
avcodec_get_frame_defaults(lastDecodedFrame);
|
|
|
|
bytesConsumedFromDecodedFrame = 0;
|
|
|
|
int len = avcodec_decode_audio4(codecCtx, lastDecodedFrame, &gotFrame, lastReadPacket);
|
|
|
|
if (len < 0 || (!gotFrame))
|
2013-10-02 21:58:18 +00:00
|
|
|
{
|
2013-10-11 03:55:32 +00:00
|
|
|
char errbuf[4096];
|
|
|
|
av_strerror(len, errbuf, 4096);
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Error decoding: len = %d, gotFrame = %d, strerr = %s", len, gotFrame, errbuf);
|
2013-10-02 21:58:18 +00:00
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
dataSize = 0;
|
|
|
|
readNextPacket = YES;
|
2013-10-02 21:58:18 +00:00
|
|
|
}
|
2013-10-11 03:55:32 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Something has been successfully decoded
|
2013-10-11 04:52:32 +00:00
|
|
|
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels,
|
2013-10-11 03:55:32 +00:00
|
|
|
lastDecodedFrame->nb_samples,
|
|
|
|
codecCtx->sample_fmt, 1);
|
|
|
|
bytesReadFromPacket += len;
|
2013-10-02 21:58:18 +00:00
|
|
|
}
|
2013-10-10 04:23:50 +00:00
|
|
|
|
2013-10-11 03:55:32 +00:00
|
|
|
if (bytesReadFromPacket >= lastReadPacket->size)
|
|
|
|
{
|
|
|
|
// decoding consumed all the read packet - read another next time
|
|
|
|
readNextPacket = YES;
|
|
|
|
}
|
2013-10-11 04:52:32 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
lastReadPacket->data += len;
|
|
|
|
lastReadPacket->size -= len;
|
|
|
|
}
|
2013-10-02 21:58:18 +00:00
|
|
|
}
|
2013-10-11 03:55:32 +00:00
|
|
|
|
|
|
|
int toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead));
|
|
|
|
|
|
|
|
// copy decoded samples to Cog's buffer
|
|
|
|
if (!planar || channels == 1) {
|
|
|
|
memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
uint8_t * out = ( uint8_t * ) targetBuf + bytesRead;
|
|
|
|
int bytesPerSample = bitsPerSample / 8;
|
2013-10-11 04:52:32 +00:00
|
|
|
int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels;
|
2013-10-11 03:55:32 +00:00
|
|
|
int toConsumePerPlane = toConsume / channels;
|
|
|
|
for (int s = 0; s < toConsumePerPlane; s += bytesPerSample) {
|
|
|
|
for (int ch = 0; ch < channels; ++ch) {
|
|
|
|
memcpy(out, lastDecodedFrame->extended_data[ch] + bytesConsumedPerPlane + s, bytesPerSample);
|
|
|
|
out += bytesPerSample;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bytesConsumedFromDecodedFrame += toConsume;
|
|
|
|
bytesRead += toConsume;
|
|
|
|
}
|
|
|
|
|
|
|
|
int framesReadNow = bytesRead / frameSize;
|
|
|
|
if ( framesRead + framesReadNow > totalFrames )
|
|
|
|
framesReadNow = totalFrames - framesRead;
|
|
|
|
|
|
|
|
framesRead += framesReadNow;
|
|
|
|
|
|
|
|
return framesReadNow;
|
2008-02-28 05:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (long)seek:(long)frame
|
|
|
|
{
|
2013-10-11 03:55:32 +00:00
|
|
|
if (frame > totalFrames) { return -1; }
|
|
|
|
int64_t ts = frame * (formatCtx->duration) / totalFrames;
|
|
|
|
avformat_seek_file(formatCtx, -1, ts - 1000, ts, ts, AVSEEK_FLAG_ANY);
|
|
|
|
avcodec_flush_buffers(codecCtx);
|
|
|
|
readNextPacket = YES; // so we immediately read next packet
|
|
|
|
bytesConsumedFromDecodedFrame = INT_MAX; // so we immediately begin decoding next frame
|
|
|
|
|
|
|
|
return frame;
|
2008-02-28 05:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSDictionary *)properties
|
|
|
|
{
|
|
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithInt:channels], @"channels",
|
|
|
|
[NSNumber numberWithInt:bitsPerSample], @"bitsPerSample",
|
2013-10-07 18:26:23 +00:00
|
|
|
[NSNumber numberWithBool:(bitsPerSample == 8)], @"Unsigned",
|
2008-02-28 05:33:56 +00:00
|
|
|
[NSNumber numberWithFloat:frequency], @"sampleRate",
|
2013-10-05 21:15:09 +00:00
|
|
|
[NSNumber numberWithBool:floatingPoint], @"floatingPoint",
|
2008-02-28 05:33:56 +00:00
|
|
|
[NSNumber numberWithDouble:totalFrames], @"totalFrames",
|
|
|
|
[NSNumber numberWithInt:bitrate], @"bitrate",
|
2013-10-11 03:55:32 +00:00
|
|
|
[NSNumber numberWithBool:seekable], @"seekable",
|
2013-10-05 21:15:09 +00:00
|
|
|
@"host", @"endian",
|
2008-02-28 05:33:56 +00:00
|
|
|
nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
|
|
|
{
|
2013-10-11 06:51:19 +00:00
|
|
|
return [NSArray arrayWithObjects:@"wma", @"asf", @"xwma", @"tak", @"mp3", @"mp2", @"m2a", @"mpa", @"ape", @"ac3", @"dts", @"dtshd", @"at3", @"wav", @"tta", nil];
|
2008-02-28 05:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
2013-10-11 06:51:19 +00:00
|
|
|
return [NSArray arrayWithObjects:@"application/wma", @"application/x-wma", @"audio/x-wma", @"audio/x-ms-wma", @"audio/x-tak", @"audio/mpeg", @"audio/x-mp3", @"audio/x-mp2", @"audio/x-ape", @"audio/x-ac3", @"audio/x-dts", @"audio/x-dtshd", @"audio/x-at3", @"audio/wav", @"tta", nil];
|
2008-02-28 05:33:56 +00:00
|
|
|
}
|
|
|
|
|
Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to.
2013-10-21 17:54:11 +00:00
|
|
|
+ (float)priority
|
|
|
|
{
|
|
|
|
return 1.0;
|
|
|
|
}
|
2008-02-28 05:33:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-02-26 20:53:18 +00:00
|
|
|
@end
|