cog/Plugins/FFMPEG/FFMPEGDecoder.m

335 lines
9.7 KiB
Objective-C

//
// FFMPEGDecoder.m
// FFMPEG
//
// Created by Andre Reffhaug on 2/26/08.
// Copyright 2008 __MyCompanyName__. All rights reserved.
//
// test
#import "FFMPEGDecoder.h"
#import "FFMPEGFileProtocols.h"
#include <pthread.h>
#import "Logging.h"
#define ST_BUFF 2048
@implementation FFMPEGDecoder
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;
}
+ (void)initialize
{
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);
}
}
- (BOOL)open:(id<CogSource>)s
{
int errcode, i;
const char *filename = [[[[s url] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] UTF8String];
formatCtx = NULL;
totalFrames = 0;
framesRead = 0;
// register all available codecs
if ((errcode = avformat_open_input(&formatCtx, filename, NULL, NULL)) < 0)
{
char errDescr[4096];
av_strerror(errcode, errDescr, 4096);
ALog(@"ERROR OPENING FILE, errcode = %d, error = %s", errcode, errDescr);
return NO;
}
if(avformat_find_stream_info(formatCtx, NULL) < 0)
{
ALog(@"CAN'T FIND STREAM INFO!");
return NO;
}
streamIndex = -1;
for(i = 0; i < formatCtx->nb_streams; i++) {
codecCtx = formatCtx->streams[i]->codec;
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO)
{
DLog(@"audio codec found");
streamIndex = i;
break;
}
}
if ( streamIndex < 0 ) {
ALog(@"no audio codec found");
return NO;
}
AVCodec * codec = avcodec_find_decoder(codecCtx->codec_id);
if (!codec) {
ALog(@"codec not found");
return NO;
}
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
ALog(@"could not open codec");
return NO;
}
lastDecodedFrame = avcodec_alloc_frame();
avcodec_get_frame_defaults(lastDecodedFrame);
lastReadPacket = malloc(sizeof(AVPacket));
av_new_packet(lastReadPacket, 0);
readNextPacket = YES;
bytesConsumedFromDecodedFrame = INT_MAX;
frequency = codecCtx->sample_rate;
channels = codecCtx->channels;
floatingPoint = NO;
switch (codecCtx->sample_fmt) {
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:
bitsPerSample = 32;
break;
case AV_SAMPLE_FMT_FLT:
case AV_SAMPLE_FMT_FLTP:
bitsPerSample = 32;
floatingPoint = YES;
break;
case AV_SAMPLE_FMT_DBL:
case AV_SAMPLE_FMT_DBLP:
bitsPerSample = 64;
floatingPoint = YES;
break;
default:
return NO;
}
totalFrames = codecCtx->sample_rate * ((float)formatCtx->duration/AV_TIME_BASE);
bitrate = (codecCtx->bit_rate) / 1000;
framesRead = 0;
seekable = [s seekable];
return YES;
}
- (void)close
{
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; }
}
- (int)readAudio:(void *)buf frames:(UInt32)frames
{
int frameSize = channels * (bitsPerSample / 8);
int gotFrame = 0;
int dataSize = 0;
int bytesToRead = frames * frameSize;
int bytesRead = 0;
BOOL endOfStream = NO;
int8_t* targetBuf = (int8_t*) buf;
memset(buf, 0, bytesToRead);
while (bytesRead < bytesToRead)
{
// 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)
{
// consume next chunk of encoded data from input stream
av_free_packet(lastReadPacket);
if(av_read_frame(formatCtx, lastReadPacket) < 0)
{
DLog(@"End of stream");
endOfStream = YES;
if (dataSize <= bytesConsumedFromDecodedFrame)
break; // end of stream;
}
readNextPacket = NO; // we probably won't need to consume another chunk
bytesReadFromPacket = 0; // until this one is fully decoded
}
if (dataSize <= bytesConsumedFromDecodedFrame)
{
if (endOfStream)
break;
// 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))
{
char errbuf[4096];
av_strerror(len, errbuf, 4096);
ALog(@"Error decoding: len = %d, gotFrame = %d, strerr = %s", len, gotFrame, errbuf);
dataSize = 0;
readNextPacket = YES;
}
else
{
// Something has been successfully decoded
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels,
lastDecodedFrame->nb_samples,
codecCtx->sample_fmt, 1);
bytesReadFromPacket += len;
}
if (bytesReadFromPacket >= lastReadPacket->size)
{
// decoding consumed all the read packet - read another next time
readNextPacket = YES;
}
else
{
lastReadPacket->data += len;
lastReadPacket->size -= len;
}
}
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;
int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels;
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;
}
- (long)seek:(long)frame
{
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;
}
- (NSDictionary *)properties
{
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:channels], @"channels",
[NSNumber numberWithInt:bitsPerSample], @"bitsPerSample",
[NSNumber numberWithBool:(bitsPerSample == 8)], @"Unsigned",
[NSNumber numberWithFloat:frequency], @"sampleRate",
[NSNumber numberWithBool:floatingPoint], @"floatingPoint",
[NSNumber numberWithDouble:totalFrames], @"totalFrames",
[NSNumber numberWithInt:bitrate], @"bitrate",
[NSNumber numberWithBool:seekable], @"seekable",
@"host", @"endian",
nil];
}
+ (NSArray *)fileTypes
{
return [NSArray arrayWithObjects:@"wma", @"asf", @"xwma", @"tak", @"mp3", @"mp2", @"m2a", @"mpa", @"ape", @"ac3", @"dts", @"dtshd", @"at3", @"wav", @"tta", nil];
}
+ (NSArray *)mimeTypes
{
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];
}
@end