void FFMS_AudioSource::Init(const FFMS_Index &Index, int DelayMode) { // Decode the first packet to ensure all properties are initialized // Don't cache it since it might be in the wrong format // Instead, leave it in DecodeFrame and it'll get cached later while (DecodeFrame->nb_samples == 0) DecodeNextBlock(); // Read properties of the audio which may not be available until the first // frame has been decoded FillAP(AP, CodecContext, Frames); if (AP.SampleRate <= 0 || AP.BitsPerSample <= 0) throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, "Codec returned zero size audio"); std::auto_ptr<FFMS_ResampleOptions> opt(CreateResampleOptions()); SetOutputFormat(opt.get()); if (DelayMode < FFMS_DELAY_NO_SHIFT) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Bad audio delay compensation mode"); if (DelayMode == FFMS_DELAY_NO_SHIFT) return; if (DelayMode > (signed)Index.size()) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds track index selected for audio delay compensation"); if (DelayMode >= 0 && Index[DelayMode].TT != FFMS_TYPE_VIDEO) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Audio delay compensation must be relative to a video track"); int64_t Delay = 0; if (DelayMode != FFMS_DELAY_TIME_ZERO) { if (DelayMode == FFMS_DELAY_FIRST_VIDEO_TRACK) { for (size_t i = 0; i < Index.size(); ++i) { if (Index[i].TT == FFMS_TYPE_VIDEO && !Index[i].empty()) { DelayMode = i; break; } } } if (DelayMode >= 0) { const FFMS_Track &VTrack = Index[DelayMode]; Delay = -(VTrack[0].PTS * VTrack.TB.Num * AP.SampleRate / (VTrack.TB.Den * 1000)); } } if (Frames.HasTS) { int i = 0; while (Frames[i].PTS == ffms_av_nopts_value) ++i; Delay += Frames[i].PTS * Frames.TB.Num * AP.SampleRate / (Frames.TB.Den * 1000); for (; i >= 0; --i) Delay -= Frames[i].SampleCount; } AP.NumSamples += Delay; }
void FFLAVFAudio::Seek() { size_t TargetPacket = GetSeekablePacketNumber(Frames, PacketNumber); LastValidTS = AV_NOPTS_VALUE; int Flags = Frames.HasTS ? AVSEEK_FLAG_BACKWARD : AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_BYTE; if (av_seek_frame(FormatContext, TrackNumber, FrameTS(TargetPacket), Flags) < 0) av_seek_frame(FormatContext, TrackNumber, FrameTS(TargetPacket), Flags | AVSEEK_FLAG_ANY); if (TargetPacket != PacketNumber) { // Decode until the PTS changes so we know where we are int64_t LastPTS = FrameTS(PacketNumber); while (LastPTS == FrameTS(PacketNumber)) DecodeNextBlock(); } }
void FFMS_AudioSource::CacheBeginning() { // Nothing to do if the cache is already populated if (!Cache.empty()) return; // The first frame is already decoded, so add it to the cache CacheIterator it = Cache.end(); CacheBlock(it); // The first packet after a seek is often decoded incorrectly, which // makes it impossible to ever correctly seek back to the beginning, so // store the first block now // In addition, anything with the same PTS as the first packet can't be // distinguished from the first packet and so can't be seeked to, so // store those as well // Some of LAVF's splitters don't like to seek to the beginning of the // file (ts and?), so cache a few blocks even if PTSes are unique // Packet 7 is the last packet I've had be unseekable to, so cache up to // 10 for a bit of an extra buffer CacheIterator end = Cache.end(); while (PacketNumber < Frames.size() && ((Frames[0].PTS != ffms_av_nopts_value && Frames[PacketNumber].PTS == Frames[0].PTS) || Cache.size() < 10)) { // Vorbis in particular seems to like having 60+ packets at the start // of the file with a PTS of 0, so we might need to expand the search // range to account for that. // Expanding slightly before it's strictly needed to ensure there's a // bit of space for an actual cache if (Cache.size() >= MaxCacheBlocks - 5) { if (MaxCacheBlocks >= EXCESSIVE_CACHE_SIZE) throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_ALLOCATION_FAILED, "Exceeded the search range for an initial valid audio PTS"); MaxCacheBlocks *= 2; } DecodeNextBlock(&end); } // Store the iterator to the last element of the cache which is used for // correctness rather than speed, so that when looking for one to delete // we know how much to skip CacheNoDelete = Cache.end(); --CacheNoDelete; }
void FFMS_AudioSource::GetAudio(void *Buf, int64_t Start, int64_t Count) { if (Start < 0 || Start + Count > AP.NumSamples || Count < 0) throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds audio samples requested"); CacheBeginning(); uint8_t *Dst = static_cast<uint8_t*>(Buf); // Apply audio delay (if any) and fill any samples before the start time with zero Start -= Delay; if (Start < 0) { size_t Bytes = static_cast<size_t>(BytesPerSample * FFMIN(-Start, Count)); memset(Dst, 0, Bytes); Count += Start; // Entire request was before the start of the audio if (Count <= 0) return; Start = 0; Dst += Bytes; } auto it = Cache.begin(); while (Count > 0) { // Find first useful cache block while (it != Cache.end() && it->Start + it->Samples <= Start) ++it; // Cache has the next block we want if (it != Cache.end() && it->Start <= Start) { int64_t SrcOffset = FFMAX(0, Start - it->Start); int64_t DstOffset = FFMAX(0, it->Start - Start); int64_t CopySamples = FFMIN(it->Samples - SrcOffset, Count - DstOffset); size_t Bytes = static_cast<size_t>(CopySamples * BytesPerSample); memcpy(Dst + DstOffset * BytesPerSample, &it->Data[SrcOffset * BytesPerSample], Bytes); Start += CopySamples; Count -= CopySamples; Dst += Bytes; ++it; } // Decode another block else { if (Start < CurrentSample && SeekOffset == -1) throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Audio stream is not seekable"); if (SeekOffset >= 0 && (Start < CurrentSample || Start > CurrentSample + DecodeFrame->nb_samples * 5)) { FrameInfo f; f.SampleStart = Start; size_t NewPacketNumber = std::distance( Frames.begin(), std::lower_bound(Frames.begin(), Frames.end(), f, SampleStartComp)); NewPacketNumber = NewPacketNumber > static_cast<size_t>(SeekOffset + 15) ? NewPacketNumber - SeekOffset - 15 : 0; while (NewPacketNumber > 0 && !Frames[NewPacketNumber].KeyFrame) --NewPacketNumber; // Only seek forward if it'll actually result in moving forward if (Start < CurrentSample || static_cast<size_t>(NewPacketNumber) > PacketNumber) { PacketNumber = NewPacketNumber; CurrentSample = -1; DecodeFrame.reset(); avcodec_flush_buffers(CodecContext); Seek(); } } // Decode until we hit the block we want if (PacketNumber >= Frames.size()) throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Seeking is severely broken"); while (CurrentSample + DecodeFrame->nb_samples <= Start && PacketNumber < Frames.size()) DecodeNextBlock(&it); if (CurrentSample > Start) throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Seeking is severely broken"); // The block we want is now in the cache immediately before it --it; } } }