diff --git a/Plugins/FFMPEG/FFMPEGDecoder.h b/Plugins/FFMPEG/FFMPEGDecoder.h index 241f826fe..34127e66b 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.h +++ b/Plugins/FFMPEG/FFMPEGDecoder.h @@ -16,26 +16,24 @@ @interface FFMPEGDecoder : NSObject { - id source; - void *sampleBuffer; - int sampleBufferSize; - int numFrames; - int samplePos; - - int streamIndex; - AVFormatContext *ic; - AVCodecContext *c; - AVCodec *codec; - - BOOL seekable; + BOOL seekable; + int channels; + int bitsPerSample; BOOL floatingPoint; - int bitsPerSample; - int bitrate; - int channels; - float frequency; - long totalFrames; - long framesPlayed; - long framesToSkip; + float frequency; + long totalFrames; + long framesRead; + int bitrate; + +@private + int streamIndex; + AVFormatContext *formatCtx; + AVCodecContext *codecCtx; + AVFrame *lastDecodedFrame; + AVPacket *lastReadPacket; + int bytesConsumedFromDecodedFrame; + int bytesReadFromPacket; + BOOL readNextPacket; } @end diff --git a/Plugins/FFMPEG/FFMPEGDecoder.m b/Plugins/FFMPEG/FFMPEGDecoder.m index 91d05690d..09858d6f7 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.m +++ b/Plugins/FFMPEG/FFMPEGDecoder.m @@ -45,37 +45,45 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) + (void)initialize { - av_register_all(); - registerCogProtocols(); - av_lockmgr_register(lockmgr_callback); + 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)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 - err = avformat_open_input(&ic, filename, NULL, NULL); - - if (err < 0) + if ((errcode = avformat_open_input(&formatCtx, filename, NULL, NULL)) < 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; + } + streamIndex = -1; - for(i = 0; i < ic->nb_streams; i++) { - c = ic->streams[i]->codec; - if(c->codec_type == AVMEDIA_TYPE_AUDIO) + for(i = 0; i < formatCtx->nb_streams; i++) { + codecCtx = formatCtx->streams[i]->codec; + if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) { NSLog(@"audio codec found"); streamIndex = i; @@ -88,49 +96,29 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) return NO; } - avformat_find_stream_info(ic, NULL); - - codec = avcodec_find_decoder(c->codec_id); + AVCodec * codec = avcodec_find_decoder(codecCtx->codec_id); if (!codec) { NSLog(@"codec not found"); return NO; } - if (avcodec_open2(c, codec, NULL) < 0) { + if (avcodec_open2(codecCtx, codec, NULL) < 0) { NSLog(@"could not open codec"); return NO; } - - av_dump_format(ic, 0, filename, 0); - - AVDictionary * metadata = ic->metadata; - AVDictionaryEntry * entry; - - 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; + lastDecodedFrame = avcodec_alloc_frame(); + avcodec_get_frame_defaults(lastDecodedFrame); + lastReadPacket = malloc(sizeof(AVPacket)); + av_new_packet(lastReadPacket, 0); + readNextPacket = YES; + bytesConsumedFromDecodedFrame = 0; + + frequency = codecCtx->sample_rate; + channels = codecCtx->channels; floatingPoint = NO; - switch (c->sample_fmt) { + + switch (codecCtx->sample_fmt) { case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P: bitsPerSample = 8; @@ -161,136 +149,142 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) default: return NO; } - totalFrames = c->sample_rate * ((float)ic->duration/AV_TIME_BASE); - framesPlayed = 0; - frequency = c->sample_rate; - seekable = YES; + + totalFrames = codecCtx->sample_rate * ((float)formatCtx->duration/AV_TIME_BASE); + bitrate = (codecCtx->bit_rate) / 1000; + framesRead = 0; + + seekable = [s seekable]; return YES; - } - (void)close { - avcodec_close(c); - avformat_close_input(&ic); - av_free(sampleBuffer); - - [source close]; - [source release]; + 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 { - AVPacket framePacket; - int framesRead = 0; - - int bytesPerFrame = (bitsPerSample/8) * channels; - - while (frames > 0) - { - if (samplePos < numFrames) - { - int samplesLeft; - samplesLeft = numFrames - samplePos; - - 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) - break; - - 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 ) + int frameSize = channels * (bitsPerSample / 8); + int gotFrame = 0; + int dataSize = 0; + + int bytesToRead = frames * frameSize; + int bytesRead = 0; + + int8_t* targetBuf = (int8_t*) buf; + memset(buf, 0, bytesToRead); + + while (bytesRead < bytesToRead) + { + + if(readNextPacket) + { + // consume next chunk of encoded data from input stream + av_free_packet(lastReadPacket); + if(av_read_frame(formatCtx, lastReadPacket) < 0) { - av_free_packet( &framePacket ); - continue; + NSLog(@"End of stream"); + break; // end of stream; } - AVFrame * frame = av_frame_alloc(); - int ret, got_frame = 0; - - while ( framePacket.size && (ret = avcodec_decode_audio4(c, frame, &got_frame, &framePacket)) >= 0 ) - { - ret = FFMIN(ret, framePacket.size); - framePacket.data += ret; - framePacket.size -= ret; - - if ( !got_frame ) continue; - - 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) { - uint8_t * out = (uint8_t *)sampleBuffer + sampleBufferOffset; - int bytesPerSample = bitsPerSample / 8; - for (int s = 0; s < plane_size; s += bytesPerSample) { - for (int ch = 0; ch < channels; ++ch) { - memcpy(out, frame->extended_data[ch] + s, bytesPerSample); - out += bytesPerSample; - } - } - } - - sampleBufferOffset += plane_size * channels; - } - - av_frame_free(&frame); - - if (framePacket.data) - 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; + readNextPacket = NO; // we probably won't need to consume another chunk + bytesReadFromPacket = 0; // until this one is fully decoded } - } - - return framesRead; - + + // 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 (dataSize <= bytesConsumedFromDecodedFrame) + { + // 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); + NSLog(@"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(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 bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / bytesPerSample; + 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 { - NSLog(@"frame: %ld", frame); - AVRational time_base = ic->streams[streamIndex]->time_base; - av_seek_frame(ic, streamIndex, frame * time_base.den / time_base.num / frequency, 0); - numFrames = 0; - framesPlayed = frame; - return 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; } @@ -304,7 +298,7 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) [NSNumber numberWithBool:floatingPoint], @"floatingPoint", [NSNumber numberWithDouble:totalFrames], @"totalFrames", [NSNumber numberWithInt:bitrate], @"bitrate", - [NSNumber numberWithBool:([source seekable] && seekable)], @"seekable", + [NSNumber numberWithBool:seekable], @"seekable", @"host", @"endian", nil]; } @@ -312,12 +306,12 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) + (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 { - 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]; }