Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/AudioReaderSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ void AudioReaderSource::getNextAudioBlock(const juce::AudioSourceChannelInfo& in
}

while (remaining_samples > 0) {
const int previous_remaining = remaining_samples;
frame.reset();
try {
// Get current frame object
if (reader) {
Expand All @@ -53,9 +55,19 @@ void AudioReaderSource::getNextAudioBlock(const juce::AudioSourceChannelInfo& in

// Get audio samples
if (reader && frame) {
const int frame_samples = frame->GetAudioSamplesCount();
const int frame_channels = frame->GetAudioChannelsCount();

// Corrupt/unsupported streams can yield frames without audio data.
// Avoid a tight loop that never consumes remaining_samples.
if (frame_samples <= 0 || frame_channels <= 0) {
info.buffer->clear(remaining_position, remaining_samples);
break;
}

if (sample_position + remaining_samples <= frame->GetAudioSamplesCount()) {
// Success, we have enough samples
for (int channel = 0; channel < frame->GetAudioChannelsCount(); channel++) {
for (int channel = 0; channel < frame_channels; channel++) {
if (channel < info.buffer->getNumChannels()) {
info.buffer->addFrom(channel, remaining_position, *frame->GetAudioSampleBuffer(),
channel, sample_position, remaining_samples);
Expand All @@ -68,7 +80,12 @@ void AudioReaderSource::getNextAudioBlock(const juce::AudioSourceChannelInfo& in
} else if (sample_position + remaining_samples > frame->GetAudioSamplesCount()) {
// Not enough samples, take what we can
int amount_to_copy = frame->GetAudioSamplesCount() - sample_position;
for (int channel = 0; channel < frame->GetAudioChannelsCount(); channel++) {
if (amount_to_copy <= 0) {
info.buffer->clear(remaining_position, remaining_samples);
break;
}

for (int channel = 0; channel < frame_channels; channel++) {
if (channel < info.buffer->getNumChannels()) {
info.buffer->addFrom(channel, remaining_position, *frame->GetAudioSampleBuffer(), channel,
sample_position, amount_to_copy);
Expand All @@ -84,7 +101,14 @@ void AudioReaderSource::getNextAudioBlock(const juce::AudioSourceChannelInfo& in
frame_position += speed;
sample_position = 0; // reset for new frame
}
} else {
info.buffer->clear(remaining_position, remaining_samples);
break;
}

if (remaining_samples == previous_remaining) {
info.buffer->clear(remaining_position, remaining_samples);
break;
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/CacheMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ void CacheMemory::Add(std::shared_ptr<Frame> frame)

// Check if frame is already contained in cache
bool CacheMemory::Contains(int64_t frame_number) {
// Create a scoped lock, to protect the cache from multiple threads
const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);

if (frames.count(frame_number) > 0) {
return true;
} else {
Expand Down
137 changes: 114 additions & 23 deletions src/FFmpegReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,23 +561,83 @@ void FFmpegReader::Open() {
// Audio encoding does not typically use more than 2 threads (most codecs use 1 thread)
aCodecCtx->thread_count = std::min(FF_AUDIO_NUM_PROCESSORS, 2);

if (aCodec == NULL) {
throw InvalidCodec("A valid audio codec could not be found for this file.", path);
}
bool audio_opened = false;
if (aCodec != NULL) {
// Init options
AVDictionary *opts = NULL;
av_dict_set(&opts, "strict", "experimental", 0);

// Init options
AVDictionary *opts = NULL;
av_dict_set(&opts, "strict", "experimental", 0);
// Open audio codec
audio_opened = (avcodec_open2(aCodecCtx, aCodec, &opts) >= 0);

// Open audio codec
if (avcodec_open2(aCodecCtx, aCodec, &opts) < 0)
throw InvalidCodec("An audio codec was found, but could not be opened.", path);
// Free options
av_dict_free(&opts);
}

// Free options
av_dict_free(&opts);
if (audio_opened) {
// Update the File Info struct with audio details (if an audio stream is found)
UpdateAudioInfo();

// Disable malformed audio stream metadata (prevents divide-by-zero / invalid resampling math)
const bool invalid_audio_info =
(info.channels <= 0) ||
(info.sample_rate <= 0) ||
(info.audio_timebase.num <= 0) ||
(info.audio_timebase.den <= 0) ||
(aCodecCtx->sample_fmt == AV_SAMPLE_FMT_NONE);
if (invalid_audio_info) {
ZmqLogger::Instance()->AppendDebugMethod(
"FFmpegReader::Open (Disable invalid audio stream)",
"channels", info.channels,
"sample_rate", info.sample_rate,
"audio_timebase.num", info.audio_timebase.num,
"audio_timebase.den", info.audio_timebase.den,
"sample_fmt", static_cast<int>(aCodecCtx ? aCodecCtx->sample_fmt : AV_SAMPLE_FMT_NONE));
info.has_audio = false;
info.audio_stream_index = -1;
audioStream = -1;
packet_status.audio_eof = true;
if (aCodecCtx) {
if (avcodec_is_open(aCodecCtx)) {
avcodec_flush_buffers(aCodecCtx);
}
AV_FREE_CONTEXT(aCodecCtx);
aCodecCtx = nullptr;
}
aStream = nullptr;
}
} else {
// Keep decoding video, but disable bad/unsupported audio stream.
ZmqLogger::Instance()->AppendDebugMethod(
"FFmpegReader::Open (Audio codec unavailable; disabling audio)",
"audioStream", audioStream);
info.has_audio = false;
info.audio_stream_index = -1;
audioStream = -1;
packet_status.audio_eof = true;
if (aCodecCtx) {
AV_FREE_CONTEXT(aCodecCtx);
aCodecCtx = nullptr;
}
aStream = nullptr;
}
}

// Update the File Info struct with audio details (if an audio stream is found)
UpdateAudioInfo();
// Guard invalid frame-rate / timebase values from malformed streams.
if (info.fps.num <= 0 || info.fps.den <= 0) {
ZmqLogger::Instance()->AppendDebugMethod(
"FFmpegReader::Open (Invalid FPS detected; applying fallback)",
"fps.num", info.fps.num,
"fps.den", info.fps.den);
info.fps.num = 30;
info.fps.den = 1;
}
if (info.video_timebase.num <= 0 || info.video_timebase.den <= 0) {
ZmqLogger::Instance()->AppendDebugMethod(
"FFmpegReader::Open (Invalid video_timebase detected; applying fallback)",
"video_timebase.num", info.video_timebase.num,
"video_timebase.den", info.video_timebase.den);
info.video_timebase = info.fps.Reciprocal();
}

// Add format metadata (if any)
Expand Down Expand Up @@ -835,12 +895,20 @@ void FFmpegReader::ApplyDurationStrategy() {
}

void FFmpegReader::UpdateAudioInfo() {
const int codec_channels =
#if HAVE_CH_LAYOUT
AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->ch_layout.nb_channels;
#else
AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels;
#endif

// Set default audio channel layout (if needed)
#if HAVE_CH_LAYOUT
if (!av_channel_layout_check(&(AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->ch_layout)))
if (codec_channels > 0 &&
!av_channel_layout_check(&(AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->ch_layout)))
AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO;
#else
if (AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout == 0)
if (codec_channels > 0 && AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout == 0)
AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout = av_get_default_channel_layout(AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels);
#endif

Expand Down Expand Up @@ -2236,12 +2304,17 @@ void FFmpegReader::UpdatePTSOffset() {
int64_t FFmpegReader::ConvertVideoPTStoFrame(int64_t pts) {
// Apply PTS offset
int64_t previous_video_frame = current_video_frame;
const double fps_value = (info.fps.num > 0 && info.fps.den > 0) ? info.fps.ToDouble() : 30.0;
const double video_timebase_value =
(info.video_timebase.num > 0 && info.video_timebase.den > 0)
? info.video_timebase.ToDouble()
: (1.0 / 30.0);

// Get the video packet start time (in seconds)
double video_seconds = (double(pts) * info.video_timebase.ToDouble()) + pts_offset_seconds;
double video_seconds = (double(pts) * video_timebase_value) + pts_offset_seconds;

// Divide by the video timebase, to get the video frame number (frame # is decimal at this point)
int64_t frame = round(video_seconds * info.fps.ToDouble()) + 1;
int64_t frame = round(video_seconds * fps_value) + 1;

// Keep track of the expected video frame #
if (current_video_frame == 0)
Expand All @@ -2264,35 +2337,53 @@ int64_t FFmpegReader::ConvertVideoPTStoFrame(int64_t pts) {

// Convert Frame Number into Video PTS
int64_t FFmpegReader::ConvertFrameToVideoPTS(int64_t frame_number) {
const double fps_value = (info.fps.num > 0 && info.fps.den > 0) ? info.fps.ToDouble() : 30.0;
const double video_timebase_value =
(info.video_timebase.num > 0 && info.video_timebase.den > 0)
? info.video_timebase.ToDouble()
: (1.0 / 30.0);

// Get timestamp of this frame (in seconds)
double seconds = (double(frame_number - 1) / info.fps.ToDouble()) + pts_offset_seconds;
double seconds = (double(frame_number - 1) / fps_value) + pts_offset_seconds;

// Calculate the # of video packets in this timestamp
int64_t video_pts = round(seconds / info.video_timebase.ToDouble());
int64_t video_pts = round(seconds / video_timebase_value);

// Apply PTS offset (opposite)
return video_pts;
}

// Convert Frame Number into Video PTS
int64_t FFmpegReader::ConvertFrameToAudioPTS(int64_t frame_number) {
const double fps_value = (info.fps.num > 0 && info.fps.den > 0) ? info.fps.ToDouble() : 30.0;
const double audio_timebase_value =
(info.audio_timebase.num > 0 && info.audio_timebase.den > 0)
? info.audio_timebase.ToDouble()
: (1.0 / 48000.0);

// Get timestamp of this frame (in seconds)
double seconds = (double(frame_number - 1) / info.fps.ToDouble()) + pts_offset_seconds;
double seconds = (double(frame_number - 1) / fps_value) + pts_offset_seconds;

// Calculate the # of audio packets in this timestamp
int64_t audio_pts = round(seconds / info.audio_timebase.ToDouble());
int64_t audio_pts = round(seconds / audio_timebase_value);

// Apply PTS offset (opposite)
return audio_pts;
}

// Calculate Starting video frame and sample # for an audio PTS
AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) {
const double audio_timebase_value =
(info.audio_timebase.num > 0 && info.audio_timebase.den > 0)
? info.audio_timebase.ToDouble()
: (1.0 / 48000.0);
const double fps_value = (info.fps.num > 0 && info.fps.den > 0) ? info.fps.ToDouble() : 30.0;

// Get the audio packet start time (in seconds)
double audio_seconds = (double(pts) * info.audio_timebase.ToDouble()) + pts_offset_seconds;
double audio_seconds = (double(pts) * audio_timebase_value) + pts_offset_seconds;

// Divide by the video timebase, to get the video frame number (frame # is decimal at this point)
double frame = (audio_seconds * info.fps.ToDouble()) + 1;
double frame = (audio_seconds * fps_value) + 1;

// Frame # as a whole number (no more decimals)
int64_t whole_frame = int64_t(frame);
Expand Down
4 changes: 2 additions & 2 deletions src/Frame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,9 @@ void Frame::SetFrameNumber(int64_t new_number)
// Calculate the # of samples per video frame (for a specific frame number and frame rate)
int Frame::GetSamplesPerFrame(int64_t number, Fraction fps, int sample_rate, int channels)
{
// Directly return 0 if there are no channels
// Directly return 0 for invalid audio/frame-rate parameters
// so that we do not need to deal with NaNs later
if (channels == 0) return 0;
if (channels <= 0 || sample_rate <= 0 || fps.num <= 0 || fps.den <= 0) return 0;

// Get the total # of samples for the previous frame, and the current frame (rounded)
double fps_rate = fps.Reciprocal().ToDouble();
Expand Down
Loading