// Copyright (c) 2007-2008 Fredrik Mellbin // // 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. #include "ffmpegsource.h" int FFMatroskaAudioSource::GetTrackIndex(int Index, unsigned char ATrackType, IScriptEnvironment *Env) { if (Index == -1) for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) if (mkv_GetTrackInfo(MF, i)->Type == ATrackType) { Index = i; break; } if (Index == -1) Env->ThrowError("FFmpegSource: No %s track found", (ATrackType & TT_VIDEO) ? "video" : "audio"); if (Index <= -2) return -2; if (Index >= (int)mkv_GetNumTracks(MF)) Env->ThrowError("FFmpegSource: Invalid %s track number", (ATrackType & TT_VIDEO) ? "video" : "audio"); TrackInfo *TI = mkv_GetTrackInfo(MF, Index); if (TI->Type != ATrackType) Env->ThrowError("FFmpegSource: Selected track is not %s", (ATrackType & TT_VIDEO) ? "video" : "audio"); return Index; } FFMatroskaAudioSource::FFMatroskaAudioSource(const char *ASource, int AAudioTrack, const char *AAudioCache, IScriptEnvironment *Env) { int AudioTrack; AudioCodecContext = NULL; AVCodec *AudioCodec = NULL; TrackInfo *VideoTI = NULL; BufferSize = 0; Buffer = NULL; AudioCS = NULL; memset(&ST,0,sizeof(ST)); ST.base.read = (int (__cdecl *)(InputStream *,ulonglong,void *,int))StdIoRead; ST.base.scan = (longlong (__cdecl *)(InputStream *,ulonglong,unsigned int))StdIoScan; ST.base.getcachesize = (unsigned int (__cdecl *)(InputStream *))StdIoGetCacheSize; ST.base.geterror = (const char *(__cdecl *)(InputStream *))StdIoGetLastError; ST.base.memalloc = (void *(__cdecl *)(InputStream *,size_t))StdIoMalloc; ST.base.memrealloc = (void *(__cdecl *)(InputStream *,void *,size_t))StdIoRealloc; ST.base.memfree = (void (__cdecl *)(InputStream *,void *)) StdIoFree; ST.base.progress = (int (__cdecl *)(InputStream *,ulonglong,ulonglong))StdIoProgress; ST.fp = fopen(ASource, "rb"); if (ST.fp == NULL) Env->ThrowError("FFmpegSource: Can't open '%s': %s", ASource, strerror(errno)); setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE); MF = mkv_OpenEx(&ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); if (MF == NULL) { fclose(ST.fp); Env->ThrowError("FFmpegSource: Can't parse Matroska file: %s", ErrorMessage); } AudioTrack = GetTrackIndex(AAudioTrack, TT_AUDIO, Env); mkv_SetTrackMask(MF, ~(1 << AudioTrack)); TrackInfo *AudioTI = mkv_GetTrackInfo(MF, AudioTrack); if (AudioTI->CompEnabled) { AudioCS = cs_Create(MF, AudioTrack, ErrorMessage, sizeof(ErrorMessage)); if (AudioCS == NULL) Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage); } AudioCodecContext = avcodec_alloc_context(); AudioCodecContext->extradata = (uint8_t *)AudioTI->CodecPrivate; AudioCodecContext->extradata_size = AudioTI->CodecPrivateSize; AudioCodec = avcodec_find_decoder(MatroskaToFFCodecID(AudioTI)); if (AudioCodec == NULL) Env->ThrowError("FFmpegSource: Audio codec not found"); if (avcodec_open(AudioCodecContext, AudioCodec) < 0) Env->ThrowError("FFmpegSource: Could not open audio codec"); // Fix for ac3 and other codecs where decoding a block of audio is required to get information about it if (AudioCodecContext->channels == 0 || AudioCodecContext->sample_rate == 0) { uint64_t StartTime, EndTime, FilePos; unsigned int Track, FrameFlags, FrameSize; mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags); int Size = ReadFrame(FilePos, FrameSize, AudioCS, Env); uint8_t *Data = Buffer; while (Size > 0) { int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; int Ret = avcodec_decode_audio2(AudioCodecContext, (int16_t *)DecodingBuffer, &TempOutputBufSize, Data, Size); if (Ret < 0) Env->ThrowError("FFmpegSource: Audio decoding error"); Size -= Ret; Data += Ret; } mkv_Seek(MF, 0, MKVF_SEEK_TO_PREV_KEYFRAME); avcodec_flush_buffers(AudioCodecContext); } VI.nchannels = AudioCodecContext->channels; VI.audio_samples_per_second = AudioCodecContext->sample_rate; switch (AudioCodecContext->sample_fmt) { case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break; case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break; case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break; case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break; case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break; default: Env->ThrowError("FFmpegSource: Unsupported/unknown sample format"); } //load audio cache bool ACacheIsValid = LoadSampleInfoFromFile(AAudioCache, ASource, AudioTrack); // Needs to be indexed? if (!ACacheIsValid) { uint64_t StartTime, EndTime, FilePos; unsigned int Track, FrameFlags, FrameSize; while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { SI.push_back(SampleInfo(VI.num_audio_samples, FilePos, FrameSize, (FrameFlags & FRAME_KF) != 0)); if (AudioCodecContext->frame_size > 0) { VI.num_audio_samples += AudioCodecContext->frame_size; } else { int Size = ReadFrame(FilePos, FrameSize, AudioCS, Env); uint8_t *Data = Buffer; while (Size > 0) { int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; int Ret = avcodec_decode_audio2(AudioCodecContext, (int16_t *)DecodingBuffer, &TempOutputBufSize, Data, Size); if (Ret < 0) Env->ThrowError("FFmpegSource: Audio decoding error"); if (Ret > 0) { int DecodedSamples = (int)VI.AudioSamplesFromBytes(TempOutputBufSize); Size -= Ret; Data += Ret; VI.num_audio_samples += DecodedSamples; } } } } mkv_Seek(MF, 0, MKVF_SEEK_TO_PREV_KEYFRAME); avcodec_flush_buffers(AudioCodecContext); if (!SaveSampleInfoToFile(AAudioCache, ASource, AudioTrack)) Env->ThrowError("FFmpegSource: Failed to save audio cache index"); } if (VI.num_audio_samples == 0) Env->ThrowError("FFmpegSource: Audio track contains no samples"); } FFMatroskaAudioSource::~FFMatroskaAudioSource() { free(Buffer); mkv_Close(MF); fclose(ST.fp); if (AudioCodecContext) avcodec_close(AudioCodecContext); av_free(AudioCodecContext); } int FFMatroskaAudioSource::ReadFrame(uint64_t AFilePos, unsigned int AFrameSize, CompressedStream *ACS, IScriptEnvironment *Env) { if (ACS) { char CSBuffer[4096]; unsigned int DecompressedFrameSize = 0; cs_NextFrame(ACS, AFilePos, AFrameSize); for (;;) { int ReadBytes = cs_ReadData(ACS, CSBuffer, sizeof(CSBuffer)); if (ReadBytes < 0) Env->ThrowError("FFmpegSource: Error decompressing data: %s", cs_GetLastError(ACS)); if (ReadBytes == 0) { return DecompressedFrameSize; } if (BufferSize < DecompressedFrameSize + ReadBytes) { BufferSize = AFrameSize; Buffer = (uint8_t *)realloc(Buffer, BufferSize); if (Buffer == NULL) Env->ThrowError("FFmpegSource: Out of memory"); } memcpy(Buffer + DecompressedFrameSize, CSBuffer, ReadBytes); DecompressedFrameSize += ReadBytes; } } else { if (_fseeki64(ST.fp, AFilePos, SEEK_SET)) Env->ThrowError("FFmpegSource: fseek(): %s", strerror(errno)); if (BufferSize < AFrameSize) { BufferSize = AFrameSize; Buffer = (uint8_t *)realloc(Buffer, BufferSize); if (Buffer == NULL) Env->ThrowError("FFmpegSource: Out of memory"); } size_t ReadBytes = fread(Buffer, 1, AFrameSize, ST.fp); if (ReadBytes != AFrameSize) { if (ReadBytes == 0) { if (feof(ST.fp)) Env->ThrowError("FFmpegSource: Unexpected EOF while reading frame"); else Env->ThrowError("FFmpegSource: Error reading frame: %s", strerror(errno)); } else Env->ThrowError("FFmpegSource: Short read while reading frame"); Env->ThrowError("FFmpegSource: Unknown read error"); } return AFrameSize; } return 0; } void __stdcall FFMatroskaAudioSource::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment *Env) { size_t CurrentAudioBlock = FFMAX((int64_t)FindClosestAudioKeyFrame(Start) - 10, (int64_t)0); avcodec_flush_buffers(AudioCodecContext); memset(Buf, 0, VI.BytesFromAudioSamples(Count)); uint8_t *DstBuf = (uint8_t *)Buf; int64_t RemainingSamples = Count; int64_t DecodeCount; do { int64_t DecodeStart = SI[CurrentAudioBlock].SampleStart; int Ret = DecodeNextAudioBlock(DecodingBuffer, &DecodeCount, SI[CurrentAudioBlock].FilePos, SI[CurrentAudioBlock].FrameSize, Env); if (Ret < 0) Env->ThrowError("Bleh, bad audio decoding"); CurrentAudioBlock++; int64_t OffsetBytes = VI.BytesFromAudioSamples(FFMAX(0, Start - DecodeStart)); int64_t CopyBytes = FFMAX(0, VI.BytesFromAudioSamples(FFMIN(RemainingSamples, DecodeCount - FFMAX(0, Start - DecodeStart)))); memcpy(DstBuf, DecodingBuffer + OffsetBytes, CopyBytes); DstBuf += CopyBytes; RemainingSamples -= VI.AudioSamplesFromBytes(CopyBytes); } while (RemainingSamples > 0 && CurrentAudioBlock < SI.size()); } int FFMatroskaAudioSource::DecodeNextAudioBlock(uint8_t *ABuf, int64_t *ACount, uint64_t AFilePos, unsigned int AFrameSize, IScriptEnvironment *Env) { int Ret = -1; *ACount = 0; int FrameSize = ReadFrame(AFilePos, AFrameSize, AudioCS, Env); uint8_t *Data = Buffer; int Size = FrameSize; while (Size > 0) { int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; Ret = avcodec_decode_audio2(AudioCodecContext, (int16_t *)ABuf, &TempOutputBufSize, Data, Size); if (Ret < 0) // throw error or something? goto Done; if (Ret > 0) { Size -= Ret; Data += Ret; ABuf += TempOutputBufSize; *ACount += VI.AudioSamplesFromBytes(TempOutputBufSize); } } Done: return Ret; }