223 lines
7.6 KiB
C++
223 lines
7.6 KiB
C++
|
// Copyright (c) 2007 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 FFmpegAudioSource::GetTrackIndex(int Index, CodecType ATrackType, IScriptEnvironment *Env) {
|
||
|
if (Index == -1)
|
||
|
for (unsigned int i = 0; i < FormatContext->nb_streams; i++)
|
||
|
if (FormatContext->streams[i]->codec->codec_type == ATrackType) {
|
||
|
Index = i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (Index == -1)
|
||
|
Env->ThrowError("FFmpegSource: No %s track found", (ATrackType == CODEC_TYPE_VIDEO) ? "video" : "audio");
|
||
|
if (Index <= -2)
|
||
|
return -2;
|
||
|
|
||
|
if (Index >= (int)FormatContext->nb_streams)
|
||
|
Env->ThrowError("FFmpegSource: Invalid %s track number", (ATrackType == CODEC_TYPE_VIDEO) ? "video" : "audio");
|
||
|
|
||
|
if (FormatContext->streams[Index]->codec->codec_type != ATrackType)
|
||
|
Env->ThrowError("FFmpegSource: Selected track is not %s", (ATrackType == CODEC_TYPE_VIDEO) ? "video" : "audio");
|
||
|
|
||
|
return Index;
|
||
|
}
|
||
|
|
||
|
bool FFmpegAudioSource::LoadSampleInfoFromFile(const char *AAudioCacheFile, const char *ADemuxedAudioFile, const char *ASource, int AAudioTrack) {
|
||
|
if (!FFAudioBase::LoadSampleInfoFromFile(AAudioCacheFile, ASource, AAudioTrack))
|
||
|
return false;
|
||
|
|
||
|
char DefaultCacheFilename[1024];
|
||
|
sprintf(DefaultCacheFilename, "%s.ffasd%dcache", ASource, AAudioTrack);
|
||
|
if (!strcmp(ADemuxedAudioFile, ""))
|
||
|
ADemuxedAudioFile = DefaultCacheFilename;
|
||
|
|
||
|
RawCache = fopen(ADemuxedAudioFile, "rb");
|
||
|
if (!RawCache)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
FFmpegAudioSource::FFmpegAudioSource(const char *ASource, int AAudioTrack, const char *AAudioCache, const char *ADemuxedAudioFile, IScriptEnvironment *Env) {
|
||
|
BufferSize = 0;
|
||
|
Buffer = NULL;
|
||
|
RawCache = NULL;
|
||
|
FormatContext = NULL;
|
||
|
AudioCodecContext = NULL;
|
||
|
AVCodec *AudioCodec = NULL;
|
||
|
|
||
|
if (av_open_input_file(&FormatContext, ASource, NULL, 0, NULL) != 0)
|
||
|
Env->ThrowError("FFmpegSource: Couldn't open '%s'", ASource);
|
||
|
|
||
|
if (av_find_stream_info(FormatContext) < 0)
|
||
|
Env->ThrowError("FFmpegSource: Couldn't find stream information");
|
||
|
|
||
|
AudioTrack = GetTrackIndex(AAudioTrack, CODEC_TYPE_AUDIO, Env);
|
||
|
|
||
|
AudioCodecContext = FormatContext->streams[AudioTrack]->codec;
|
||
|
|
||
|
AudioCodec = avcodec_find_decoder(AudioCodecContext->codec_id);
|
||
|
if (AudioCodec == NULL)
|
||
|
Env->ThrowError("FFmpegSource: Audio codec not found");
|
||
|
|
||
|
if (avcodec_open(AudioCodecContext, AudioCodec) < 0)
|
||
|
Env->ThrowError("FFmpegSource: Could not open audio codec");
|
||
|
|
||
|
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 cache
|
||
|
bool ACacheIsValid = LoadSampleInfoFromFile(AAudioCache, ADemuxedAudioFile, ASource, AudioTrack);
|
||
|
|
||
|
char DefaultCacheFilename[1024];
|
||
|
sprintf(DefaultCacheFilename, "%s.ffasd%dcache", ASource, AudioTrack);
|
||
|
if (!strcmp(ADemuxedAudioFile, ""))
|
||
|
ADemuxedAudioFile = DefaultCacheFilename;
|
||
|
if (!RawCache)
|
||
|
RawCache = fopen(ADemuxedAudioFile, "wb+");
|
||
|
|
||
|
// Needs to be indexed?
|
||
|
if (!ACacheIsValid) {
|
||
|
AVPacket Packet;
|
||
|
|
||
|
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
||
|
if (Packet.stream_index == AudioTrack) {
|
||
|
SI.push_back(SampleInfo(VI.num_audio_samples, _ftelli64(RawCache), Packet.size, (Packet.flags & PKT_FLAG_KEY) ? 1 : 0));
|
||
|
fwrite(Packet.data, 1, Packet.size, RawCache);
|
||
|
|
||
|
if (AudioCodecContext->frame_size > 0) {
|
||
|
VI.num_audio_samples += AudioCodecContext->frame_size;
|
||
|
} else {
|
||
|
int Size = Packet.size;
|
||
|
uint8_t *Data = Packet.data;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
av_free_packet(&Packet);
|
||
|
}
|
||
|
|
||
|
av_seek_frame(FormatContext, AudioTrack, 0, AVSEEK_FLAG_BACKWARD);
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
int FFmpegAudioSource::DecodeNextAudioBlock(uint8_t *ABuf, int64_t *ACount, uint64_t AFilePos, unsigned int AFrameSize, IScriptEnvironment *Env) {
|
||
|
int Ret = -1;
|
||
|
*ACount = 0;
|
||
|
|
||
|
_fseeki64(RawCache, AFilePos, SEEK_SET);
|
||
|
|
||
|
if (AFrameSize > BufferSize) {
|
||
|
Buffer = (uint8_t *)realloc(Buffer, AFrameSize);
|
||
|
BufferSize = AFrameSize;
|
||
|
}
|
||
|
|
||
|
fread(Buffer, 1, AFrameSize, RawCache);
|
||
|
|
||
|
uint8_t *Data = Buffer;
|
||
|
int Size = AFrameSize;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
void FFmpegAudioSource::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 && DecodeCount > 0);
|
||
|
}
|
||
|
|
||
|
FFmpegAudioSource::~FFmpegAudioSource() {
|
||
|
if (RawCache)
|
||
|
fclose(RawCache);
|
||
|
if (AudioCodecContext)
|
||
|
avcodec_close(AudioCodecContext);
|
||
|
av_close_input_file(FormatContext);
|
||
|
}
|