Enabled APE support and reworked FFMPEG decoder according to the mamburu fork
parent
84474c5002
commit
51dc6fae15
|
@ -16,26 +16,24 @@
|
||||||
|
|
||||||
@interface FFMPEGDecoder : NSObject <CogDecoder>
|
@interface FFMPEGDecoder : NSObject <CogDecoder>
|
||||||
{
|
{
|
||||||
id<CogSource> source;
|
|
||||||
void *sampleBuffer;
|
|
||||||
int sampleBufferSize;
|
|
||||||
int numFrames;
|
|
||||||
int samplePos;
|
|
||||||
|
|
||||||
int streamIndex;
|
|
||||||
AVFormatContext *ic;
|
|
||||||
AVCodecContext *c;
|
|
||||||
AVCodec *codec;
|
|
||||||
|
|
||||||
BOOL seekable;
|
BOOL seekable;
|
||||||
BOOL floatingPoint;
|
|
||||||
int bitsPerSample;
|
|
||||||
int bitrate;
|
|
||||||
int channels;
|
int channels;
|
||||||
|
int bitsPerSample;
|
||||||
|
BOOL floatingPoint;
|
||||||
float frequency;
|
float frequency;
|
||||||
long totalFrames;
|
long totalFrames;
|
||||||
long framesPlayed;
|
long framesRead;
|
||||||
long framesToSkip;
|
int bitrate;
|
||||||
|
|
||||||
|
@private
|
||||||
|
int streamIndex;
|
||||||
|
AVFormatContext *formatCtx;
|
||||||
|
AVCodecContext *codecCtx;
|
||||||
|
AVFrame *lastDecodedFrame;
|
||||||
|
AVPacket *lastReadPacket;
|
||||||
|
int bytesConsumedFromDecodedFrame;
|
||||||
|
int bytesReadFromPacket;
|
||||||
|
BOOL readNextPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -45,37 +45,45 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op)
|
||||||
|
|
||||||
+ (void)initialize
|
+ (void)initialize
|
||||||
{
|
{
|
||||||
|
if(self == [FFMPEGDecoder class])
|
||||||
|
{
|
||||||
|
av_log_set_flags(AV_LOG_SKIP_REPEATED);
|
||||||
|
av_log_set_level(AV_LOG_ERROR);
|
||||||
av_register_all();
|
av_register_all();
|
||||||
registerCogProtocols();
|
registerCogProtocols();
|
||||||
av_lockmgr_register(lockmgr_callback);
|
av_lockmgr_register(lockmgr_callback);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)open:(id<CogSource>)s
|
- (BOOL)open:(id<CogSource>)s
|
||||||
{
|
{
|
||||||
source = [s retain];
|
int errcode, i;
|
||||||
|
const char *filename = [[[[s url] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] UTF8String];
|
||||||
|
|
||||||
|
formatCtx = NULL;
|
||||||
|
totalFrames = 0;
|
||||||
|
framesRead = 0;
|
||||||
|
|
||||||
int err, i;
|
|
||||||
const char *filename = [[[[source url] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] UTF8String];
|
|
||||||
|
|
||||||
ic = NULL;
|
|
||||||
numFrames = 0;
|
|
||||||
samplePos = 0;
|
|
||||||
sampleBuffer = NULL;
|
|
||||||
// register all available codecs
|
// register all available codecs
|
||||||
|
|
||||||
err = avformat_open_input(&ic, filename, NULL, NULL);
|
if ((errcode = avformat_open_input(&formatCtx, filename, NULL, NULL)) < 0)
|
||||||
|
|
||||||
if (err < 0)
|
|
||||||
{
|
{
|
||||||
NSLog(@"Opening file failed horribly: %d", err);
|
char errDescr[4096];
|
||||||
|
av_strerror(errcode, errDescr, 4096);
|
||||||
|
NSLog(@"ERROR OPENING FILE, errcode = %d, error = %s", errcode, errDescr);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(avformat_find_stream_info(formatCtx, NULL) < 0)
|
||||||
|
{
|
||||||
|
NSLog(@"CAN'T FIND STREAM INFO!");
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
streamIndex = -1;
|
streamIndex = -1;
|
||||||
for(i = 0; i < ic->nb_streams; i++) {
|
for(i = 0; i < formatCtx->nb_streams; i++) {
|
||||||
c = ic->streams[i]->codec;
|
codecCtx = formatCtx->streams[i]->codec;
|
||||||
if(c->codec_type == AVMEDIA_TYPE_AUDIO)
|
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||||
{
|
{
|
||||||
NSLog(@"audio codec found");
|
NSLog(@"audio codec found");
|
||||||
streamIndex = i;
|
streamIndex = i;
|
||||||
|
@ -88,49 +96,29 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op)
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
avformat_find_stream_info(ic, NULL);
|
AVCodec * codec = avcodec_find_decoder(codecCtx->codec_id);
|
||||||
|
|
||||||
codec = avcodec_find_decoder(c->codec_id);
|
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
NSLog(@"codec not found");
|
NSLog(@"codec not found");
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avcodec_open2(c, codec, NULL) < 0) {
|
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
|
||||||
NSLog(@"could not open codec");
|
NSLog(@"could not open codec");
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_dump_format(ic, 0, filename, 0);
|
lastDecodedFrame = avcodec_alloc_frame();
|
||||||
|
avcodec_get_frame_defaults(lastDecodedFrame);
|
||||||
|
lastReadPacket = malloc(sizeof(AVPacket));
|
||||||
|
av_new_packet(lastReadPacket, 0);
|
||||||
|
readNextPacket = YES;
|
||||||
|
bytesConsumedFromDecodedFrame = 0;
|
||||||
|
|
||||||
AVDictionary * metadata = ic->metadata;
|
frequency = codecCtx->sample_rate;
|
||||||
AVDictionaryEntry * entry;
|
channels = codecCtx->channels;
|
||||||
|
|
||||||
if ((entry = av_dict_get(metadata, "title", NULL, 0)))
|
|
||||||
NSLog(@"Title: %s", entry->value);
|
|
||||||
if ((entry = av_dict_get(metadata, "author", NULL, 0)))
|
|
||||||
NSLog(@"Author: %s", entry->value);
|
|
||||||
if ((entry = av_dict_get(metadata, "album", NULL, 0)))
|
|
||||||
NSLog(@"Album: %s", entry->value);
|
|
||||||
if ((entry = av_dict_get(metadata, "year", NULL, 0)))
|
|
||||||
NSLog(@"Year: %s", entry->value);
|
|
||||||
if ((entry = av_dict_get(metadata, "track", NULL, 0)))
|
|
||||||
NSLog(@"Track: %s", entry->value);
|
|
||||||
if ((entry = av_dict_get(metadata, "genre", NULL, 0)))
|
|
||||||
NSLog(@"Genre: %s", entry->value);
|
|
||||||
if ((entry = av_dict_get(metadata, "copyright", NULL, 0)))
|
|
||||||
NSLog(@"Copyright: %s", entry->value);
|
|
||||||
if ((entry = av_dict_get(metadata, "comment", NULL, 0)))
|
|
||||||
NSLog(@"Comments: %s", entry->value);
|
|
||||||
|
|
||||||
NSLog(@"bitrate: %d", ic->bit_rate);
|
|
||||||
NSLog(@"sample rate: %d", c->sample_rate);
|
|
||||||
NSLog(@"channels: %d", c->channels);
|
|
||||||
|
|
||||||
channels = c->channels;
|
|
||||||
bitrate = c->bit_rate / 1000;
|
|
||||||
floatingPoint = NO;
|
floatingPoint = NO;
|
||||||
switch (c->sample_fmt) {
|
|
||||||
|
switch (codecCtx->sample_fmt) {
|
||||||
case AV_SAMPLE_FMT_U8:
|
case AV_SAMPLE_FMT_U8:
|
||||||
case AV_SAMPLE_FMT_U8P:
|
case AV_SAMPLE_FMT_U8P:
|
||||||
bitsPerSample = 8;
|
bitsPerSample = 8;
|
||||||
|
@ -161,135 +149,141 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op)
|
||||||
default:
|
default:
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
totalFrames = c->sample_rate * ((float)ic->duration/AV_TIME_BASE);
|
|
||||||
framesPlayed = 0;
|
totalFrames = codecCtx->sample_rate * ((float)formatCtx->duration/AV_TIME_BASE);
|
||||||
frequency = c->sample_rate;
|
bitrate = (codecCtx->bit_rate) / 1000;
|
||||||
seekable = YES;
|
framesRead = 0;
|
||||||
|
|
||||||
|
seekable = [s seekable];
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)close
|
- (void)close
|
||||||
{
|
{
|
||||||
avcodec_close(c);
|
if (lastReadPacket)
|
||||||
avformat_close_input(&ic);
|
{
|
||||||
av_free(sampleBuffer);
|
av_free_packet(lastReadPacket);
|
||||||
|
free(lastReadPacket);
|
||||||
|
lastReadPacket = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
[source close];
|
if (lastDecodedFrame) { av_free(lastDecodedFrame); lastDecodedFrame = NULL; }
|
||||||
[source release];
|
|
||||||
|
if (codecCtx) { avcodec_close(codecCtx); codecCtx = NULL; }
|
||||||
|
|
||||||
|
if (formatCtx) { avformat_close_input(&(formatCtx)); formatCtx = NULL; }
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
||||||
{
|
{
|
||||||
AVPacket framePacket;
|
int frameSize = channels * (bitsPerSample / 8);
|
||||||
int framesRead = 0;
|
int gotFrame = 0;
|
||||||
|
int dataSize = 0;
|
||||||
|
|
||||||
int bytesPerFrame = (bitsPerSample/8) * channels;
|
int bytesToRead = frames * frameSize;
|
||||||
|
int bytesRead = 0;
|
||||||
|
|
||||||
while (frames > 0)
|
int8_t* targetBuf = (int8_t*) buf;
|
||||||
|
memset(buf, 0, bytesToRead);
|
||||||
|
|
||||||
|
while (bytesRead < bytesToRead)
|
||||||
{
|
{
|
||||||
if (samplePos < numFrames)
|
|
||||||
|
if(readNextPacket)
|
||||||
{
|
{
|
||||||
int samplesLeft;
|
// consume next chunk of encoded data from input stream
|
||||||
samplesLeft = numFrames - samplePos;
|
av_free_packet(lastReadPacket);
|
||||||
|
if(av_read_frame(formatCtx, lastReadPacket) < 0)
|
||||||
if (samplesLeft > frames)
|
|
||||||
samplesLeft = frames;
|
|
||||||
|
|
||||||
memcpy(buf, sampleBuffer + (samplePos * bytesPerFrame), samplesLeft * bytesPerFrame);
|
|
||||||
buf += samplesLeft * bytesPerFrame;
|
|
||||||
framesRead += samplesLeft;
|
|
||||||
frames -= samplesLeft;
|
|
||||||
samplePos += samplesLeft;
|
|
||||||
}
|
|
||||||
if (frames > 0)
|
|
||||||
{
|
{
|
||||||
if (framesPlayed >= totalFrames)
|
NSLog(@"End of stream");
|
||||||
break;
|
break; // end of stream;
|
||||||
|
|
||||||
size_t sampleBufferOffset = 0;
|
|
||||||
|
|
||||||
|
|
||||||
if (av_read_frame(ic, &framePacket) < 0)
|
|
||||||
{
|
|
||||||
NSLog(@"Uh oh... av_read_frame returned negative");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( framePacket.stream_index != streamIndex )
|
readNextPacket = NO; // we probably won't need to consume another chunk
|
||||||
{
|
bytesReadFromPacket = 0; // until this one is fully decoded
|
||||||
av_free_packet( &framePacket );
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AVFrame * frame = av_frame_alloc();
|
// buffer size needed to hold decoded samples, in bytes
|
||||||
int ret, got_frame = 0;
|
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);
|
||||||
|
|
||||||
while ( framePacket.size && (ret = avcodec_decode_audio4(c, frame, &got_frame, &framePacket)) >= 0 )
|
if (dataSize <= bytesConsumedFromDecodedFrame)
|
||||||
{
|
{
|
||||||
ret = FFMIN(ret, framePacket.size);
|
// consumed all decoded samples - decode more
|
||||||
framePacket.data += ret;
|
avcodec_get_frame_defaults(lastDecodedFrame);
|
||||||
framePacket.size -= ret;
|
bytesConsumedFromDecodedFrame = 0;
|
||||||
|
int len = avcodec_decode_audio4(codecCtx, lastDecodedFrame, &gotFrame, lastReadPacket);
|
||||||
|
if (len < 0 || (!gotFrame))
|
||||||
|
{
|
||||||
|
char errbuf[4096];
|
||||||
|
av_strerror(len, errbuf, 4096);
|
||||||
|
NSLog(@"Error decoding: len = %d, gotFrame = %d, strerr = %s", len, gotFrame, errbuf);
|
||||||
|
|
||||||
if ( !got_frame ) continue;
|
dataSize = 0;
|
||||||
|
readNextPacket = YES;
|
||||||
int plane_size;
|
|
||||||
int planar = av_sample_fmt_is_planar(c->sample_fmt);
|
|
||||||
int data_size = av_samples_get_buffer_size(&plane_size, c->channels,
|
|
||||||
frame->nb_samples,
|
|
||||||
c->sample_fmt, 1);
|
|
||||||
|
|
||||||
sampleBuffer = av_realloc(sampleBuffer, sampleBufferOffset + data_size);
|
|
||||||
|
|
||||||
if (!planar) {
|
|
||||||
memcpy((uint8_t *)sampleBuffer + sampleBufferOffset, frame->extended_data[0], plane_size);
|
|
||||||
}
|
}
|
||||||
else if (channels > 1) {
|
else
|
||||||
uint8_t * out = (uint8_t *)sampleBuffer + sampleBufferOffset;
|
{
|
||||||
|
// Something has been successfully decoded
|
||||||
|
dataSize = av_samples_get_buffer_size(NULL, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bytesPerSample = bitsPerSample / 8;
|
||||||
for (int s = 0; s < plane_size; s += bytesPerSample) {
|
int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / bytesPerSample;
|
||||||
|
int toConsumePerPlane = toConsume / channels;
|
||||||
|
for (int s = 0; s < toConsumePerPlane; s += bytesPerSample) {
|
||||||
for (int ch = 0; ch < channels; ++ch) {
|
for (int ch = 0; ch < channels; ++ch) {
|
||||||
memcpy(out, frame->extended_data[ch] + s, bytesPerSample);
|
memcpy(out, lastDecodedFrame->extended_data[ch] + bytesConsumedPerPlane + s, bytesPerSample);
|
||||||
out += bytesPerSample;
|
out += bytesPerSample;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sampleBufferOffset += plane_size * channels;
|
bytesConsumedFromDecodedFrame += toConsume;
|
||||||
|
bytesRead += toConsume;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_frame_free(&frame);
|
int framesReadNow = bytesRead / frameSize;
|
||||||
|
if ( framesRead + framesReadNow > totalFrames )
|
||||||
|
framesReadNow = totalFrames - framesRead;
|
||||||
|
|
||||||
if (framePacket.data)
|
framesRead += framesReadNow;
|
||||||
av_free_packet(&framePacket);
|
|
||||||
|
|
||||||
if ( !sampleBufferOffset ) {
|
|
||||||
if ( ret < 0 ) break;
|
|
||||||
else continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
numFrames = sampleBufferOffset / bytesPerFrame;
|
|
||||||
samplePos = 0;
|
|
||||||
|
|
||||||
if (numFrames + framesPlayed > totalFrames)
|
|
||||||
numFrames = totalFrames - framesPlayed;
|
|
||||||
|
|
||||||
framesPlayed += numFrames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return framesRead;
|
|
||||||
|
|
||||||
|
return framesReadNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (long)seek:(long)frame
|
- (long)seek:(long)frame
|
||||||
{
|
{
|
||||||
NSLog(@"frame: %ld", frame);
|
if (frame > totalFrames) { return -1; }
|
||||||
AVRational time_base = ic->streams[streamIndex]->time_base;
|
int64_t ts = frame * (formatCtx->duration) / totalFrames;
|
||||||
av_seek_frame(ic, streamIndex, frame * time_base.den / time_base.num / frequency, 0);
|
avformat_seek_file(formatCtx, -1, ts - 1000, ts, ts, AVSEEK_FLAG_ANY);
|
||||||
numFrames = 0;
|
avcodec_flush_buffers(codecCtx);
|
||||||
framesPlayed = frame;
|
readNextPacket = YES; // so we immediately read next packet
|
||||||
|
bytesConsumedFromDecodedFrame = INT_MAX; // so we immediately begin decoding next frame
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +298,7 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op)
|
||||||
[NSNumber numberWithBool:floatingPoint], @"floatingPoint",
|
[NSNumber numberWithBool:floatingPoint], @"floatingPoint",
|
||||||
[NSNumber numberWithDouble:totalFrames], @"totalFrames",
|
[NSNumber numberWithDouble:totalFrames], @"totalFrames",
|
||||||
[NSNumber numberWithInt:bitrate], @"bitrate",
|
[NSNumber numberWithInt:bitrate], @"bitrate",
|
||||||
[NSNumber numberWithBool:([source seekable] && seekable)], @"seekable",
|
[NSNumber numberWithBool:seekable], @"seekable",
|
||||||
@"host", @"endian",
|
@"host", @"endian",
|
||||||
nil];
|
nil];
|
||||||
}
|
}
|
||||||
|
@ -312,12 +306,12 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op)
|
||||||
|
|
||||||
+ (NSArray *)fileTypes
|
+ (NSArray *)fileTypes
|
||||||
{
|
{
|
||||||
return [NSArray arrayWithObjects:@"wma", @"asf", @"xwma", @"tak", @"mp3", @"mp2", @"m2a", @"mpa", nil];
|
return [NSArray arrayWithObjects:@"wma", @"asf", @"xwma", @"tak", @"mp3", @"mp2", @"m2a", @"mpa", @"ape", nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSArray *)mimeTypes
|
+ (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", nil];
|
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", nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue