2018-02-01 09:11:04 -02:00
|
|
|
/*******************************************************************************
|
2024-12-03 14:47:25 +01:00
|
|
|
* Copyright 2009-2024 Jörg Müller
|
2018-02-01 09:11:04 -02:00
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "FFMPEGReader.h"
|
|
|
|
|
#include "Exception.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
|
#include <libavformat/avio.h>
|
2018-06-08 23:10:52 +02:00
|
|
|
#include <libavutil/avutil.h>
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AUD_NAMESPACE_BEGIN
|
|
|
|
|
|
2024-11-18 17:46:04 +01:00
|
|
|
/* FFmpeg < 4.0 */
|
2018-06-08 23:10:52 +02:00
|
|
|
#if LIBAVCODEC_VERSION_MAJOR < 58
|
|
|
|
|
#define FFMPEG_OLD_CODE
|
|
|
|
|
#endif
|
2024-11-18 17:46:04 +01:00
|
|
|
/* FFmpeg < 5.0 */
|
|
|
|
|
#if LIBAVCODEC_VERSION_MAJOR < 59
|
|
|
|
|
#define FFMPEG_OLD_CH_LAYOUT
|
|
|
|
|
#endif
|
2018-06-08 23:10:52 +02:00
|
|
|
|
2021-08-30 22:36:02 +02:00
|
|
|
SampleFormat FFMPEGReader::convertSampleFormat(AVSampleFormat format)
|
|
|
|
|
{
|
|
|
|
|
switch(av_get_packed_sample_fmt(format))
|
|
|
|
|
{
|
|
|
|
|
case AV_SAMPLE_FMT_U8:
|
|
|
|
|
return FORMAT_U8;
|
|
|
|
|
case AV_SAMPLE_FMT_S16:
|
|
|
|
|
return FORMAT_S16;
|
|
|
|
|
case AV_SAMPLE_FMT_S32:
|
|
|
|
|
return FORMAT_S32;
|
|
|
|
|
case AV_SAMPLE_FMT_FLT:
|
|
|
|
|
return FORMAT_FLOAT32;
|
|
|
|
|
case AV_SAMPLE_FMT_DBL:
|
|
|
|
|
return FORMAT_FLOAT64;
|
|
|
|
|
default:
|
|
|
|
|
AUD_THROW(FileException, "FFMPEG sample format unknown.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-01 09:11:04 -02:00
|
|
|
int FFMPEGReader::decode(AVPacket& packet, Buffer& buffer)
|
|
|
|
|
{
|
2018-06-08 23:10:52 +02:00
|
|
|
int buf_size = buffer.getSize();
|
|
|
|
|
int buf_pos = 0;
|
|
|
|
|
|
|
|
|
|
#ifdef FFMPEG_OLD_CODE
|
2018-02-01 09:11:04 -02:00
|
|
|
int got_frame;
|
|
|
|
|
int read_length;
|
|
|
|
|
uint8_t* orig_data = packet.data;
|
|
|
|
|
int orig_size = packet.size;
|
|
|
|
|
|
|
|
|
|
while(packet.size > 0)
|
|
|
|
|
{
|
|
|
|
|
got_frame = 0;
|
|
|
|
|
|
2018-06-08 23:10:52 +02:00
|
|
|
read_length = avcodec_decode_audio4(m_codecCtx, m_frame, &got_frame, &packet);
|
2018-02-01 09:11:04 -02:00
|
|
|
if(read_length < 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if(got_frame)
|
|
|
|
|
{
|
2018-06-08 23:10:52 +02:00
|
|
|
int data_size = av_samples_get_buffer_size(nullptr, m_codecCtx->channels, m_frame->nb_samples, m_codecCtx->sample_fmt, 1);
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
if(buf_size - buf_pos < data_size)
|
|
|
|
|
{
|
|
|
|
|
buffer.resize(buf_size + data_size, true);
|
|
|
|
|
buf_size += data_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(m_tointerleave)
|
|
|
|
|
{
|
2018-06-08 23:10:52 +02:00
|
|
|
int single_size = data_size / m_codecCtx->channels / m_frame->nb_samples;
|
2018-02-01 09:11:04 -02:00
|
|
|
for(int channel = 0; channel < m_codecCtx->channels; channel++)
|
|
|
|
|
{
|
2018-06-08 23:10:52 +02:00
|
|
|
for(int i = 0; i < m_frame->nb_samples; i++)
|
2018-02-01 09:11:04 -02:00
|
|
|
{
|
|
|
|
|
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos + ((m_codecCtx->channels * i) + channel) * single_size,
|
2021-08-30 22:36:02 +02:00
|
|
|
m_frame->data[channel] + i * single_size, single_size);
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2018-06-08 23:10:52 +02:00
|
|
|
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos, m_frame->data[0], data_size);
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
buf_pos += data_size;
|
|
|
|
|
}
|
|
|
|
|
packet.size -= read_length;
|
|
|
|
|
packet.data += read_length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
packet.data = orig_data;
|
|
|
|
|
packet.size = orig_size;
|
2018-06-08 23:10:52 +02:00
|
|
|
#else
|
|
|
|
|
avcodec_send_packet(m_codecCtx, &packet);
|
|
|
|
|
|
|
|
|
|
while(true)
|
|
|
|
|
{
|
|
|
|
|
auto ret = avcodec_receive_frame(m_codecCtx, m_frame);
|
|
|
|
|
|
|
|
|
|
if(ret != 0)
|
|
|
|
|
break;
|
|
|
|
|
|
2024-11-18 17:46:04 +01:00
|
|
|
#ifdef FFMPEG_OLD_CH_LAYOUT
|
|
|
|
|
int channels = m_codecCtx->channels;
|
|
|
|
|
#else
|
|
|
|
|
int channels = m_codecCtx->ch_layout.nb_channels;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
int data_size = av_samples_get_buffer_size(nullptr, channels, m_frame->nb_samples, m_codecCtx->sample_fmt, 1);
|
2018-06-08 23:10:52 +02:00
|
|
|
|
|
|
|
|
if(buf_size - buf_pos < data_size)
|
|
|
|
|
{
|
|
|
|
|
buffer.resize(buf_size + data_size, true);
|
|
|
|
|
buf_size += data_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(m_tointerleave)
|
|
|
|
|
{
|
2024-11-18 17:46:04 +01:00
|
|
|
int single_size = data_size / channels / m_frame->nb_samples;
|
|
|
|
|
for(int channel = 0; channel < channels; channel++)
|
2018-06-08 23:10:52 +02:00
|
|
|
{
|
|
|
|
|
for(int i = 0; i < m_frame->nb_samples; i++)
|
|
|
|
|
{
|
2024-11-18 17:46:04 +01:00
|
|
|
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos + ((channels * i) + channel) * single_size,
|
2021-08-30 22:36:02 +02:00
|
|
|
m_frame->data[channel] + i * single_size, single_size);
|
2018-06-08 23:10:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos, m_frame->data[0], data_size);
|
|
|
|
|
|
|
|
|
|
buf_pos += data_size;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
return buf_pos;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 22:36:02 +02:00
|
|
|
void FFMPEGReader::init(int stream)
|
2018-02-01 09:11:04 -02:00
|
|
|
{
|
|
|
|
|
m_position = 0;
|
|
|
|
|
m_pkgbuf_left = 0;
|
|
|
|
|
|
|
|
|
|
if(avformat_find_stream_info(m_formatCtx, nullptr) < 0)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, ffmpeg couldn't find the stream info.");
|
|
|
|
|
|
|
|
|
|
// find audio stream and codec
|
|
|
|
|
m_stream = -1;
|
|
|
|
|
|
|
|
|
|
for(unsigned int i = 0; i < m_formatCtx->nb_streams; i++)
|
|
|
|
|
{
|
2018-06-08 23:10:52 +02:00
|
|
|
#ifdef FFMPEG_OLD_CODE
|
2021-08-30 22:36:02 +02:00
|
|
|
if((m_formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
2018-06-08 23:10:52 +02:00
|
|
|
#else
|
2021-08-30 22:36:02 +02:00
|
|
|
if((m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
2018-06-08 23:10:52 +02:00
|
|
|
#endif
|
2021-08-30 22:36:02 +02:00
|
|
|
&& (m_stream < 0))
|
2018-02-01 09:11:04 -02:00
|
|
|
{
|
2021-08-30 22:36:02 +02:00
|
|
|
if(stream == 0)
|
2021-07-06 19:48:06 +02:00
|
|
|
{
|
2021-08-30 22:36:02 +02:00
|
|
|
m_stream=i;
|
|
|
|
|
break;
|
2021-07-06 19:48:06 +02:00
|
|
|
}
|
|
|
|
|
else
|
2021-08-30 22:36:02 +02:00
|
|
|
stream--;
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(m_stream == -1)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, no audio stream found by ffmpeg.");
|
|
|
|
|
|
|
|
|
|
// get a decoder and open it
|
2018-06-08 23:10:52 +02:00
|
|
|
#ifndef FFMPEG_OLD_CODE
|
2022-02-18 18:20:06 +01:00
|
|
|
const AVCodec* aCodec = avcodec_find_decoder(m_formatCtx->streams[m_stream]->codecpar->codec_id);
|
2018-06-08 23:10:52 +02:00
|
|
|
|
2018-02-01 09:11:04 -02:00
|
|
|
if(!aCodec)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, no decoder found with ffmpeg.");
|
2018-06-08 23:10:52 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
m_frame = av_frame_alloc();
|
|
|
|
|
|
|
|
|
|
if(!m_frame)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, ffmpeg frame couldn't be allocated.");
|
|
|
|
|
|
|
|
|
|
#ifdef FFMPEG_OLD_CODE
|
|
|
|
|
m_codecCtx = m_formatCtx->streams[m_stream]->codec;
|
|
|
|
|
|
|
|
|
|
AVCodec* aCodec = avcodec_find_decoder(m_codecCtx->codec_id);
|
|
|
|
|
#else
|
|
|
|
|
m_codecCtx = avcodec_alloc_context3(aCodec);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if(!m_codecCtx)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, ffmpeg context couldn't be allocated.");
|
|
|
|
|
|
|
|
|
|
#ifndef FFMPEG_OLD_CODE
|
|
|
|
|
if(avcodec_parameters_to_context(m_codecCtx, m_formatCtx->streams[m_stream]->codecpar) < 0)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, ffmpeg decoder parameters couldn't be copied to decoder context.");
|
|
|
|
|
#endif
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
if(avcodec_open2(m_codecCtx, aCodec, nullptr) < 0)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, ffmpeg codec couldn't be opened.");
|
|
|
|
|
|
2024-11-18 17:46:04 +01:00
|
|
|
#ifdef FFMPEG_OLD_CH_LAYOUT
|
|
|
|
|
int channels = m_codecCtx->channels;
|
|
|
|
|
#else
|
|
|
|
|
int channels = m_codecCtx->ch_layout.nb_channels;
|
|
|
|
|
#endif
|
|
|
|
|
m_specs.channels = (Channels) channels;
|
2018-02-01 09:11:04 -02:00
|
|
|
m_tointerleave = av_sample_fmt_is_planar(m_codecCtx->sample_fmt);
|
|
|
|
|
|
|
|
|
|
switch(av_get_packed_sample_fmt(m_codecCtx->sample_fmt))
|
|
|
|
|
{
|
|
|
|
|
case AV_SAMPLE_FMT_U8:
|
|
|
|
|
m_convert = convert_u8_float;
|
|
|
|
|
m_specs.format = FORMAT_U8;
|
|
|
|
|
break;
|
|
|
|
|
case AV_SAMPLE_FMT_S16:
|
|
|
|
|
m_convert = convert_s16_float;
|
|
|
|
|
m_specs.format = FORMAT_S16;
|
|
|
|
|
break;
|
|
|
|
|
case AV_SAMPLE_FMT_S32:
|
|
|
|
|
m_convert = convert_s32_float;
|
|
|
|
|
m_specs.format = FORMAT_S32;
|
|
|
|
|
break;
|
|
|
|
|
case AV_SAMPLE_FMT_FLT:
|
|
|
|
|
m_convert = convert_copy<float>;
|
|
|
|
|
m_specs.format = FORMAT_FLOAT32;
|
|
|
|
|
break;
|
|
|
|
|
case AV_SAMPLE_FMT_DBL:
|
|
|
|
|
m_convert = convert_double_float;
|
|
|
|
|
m_specs.format = FORMAT_FLOAT64;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be read, ffmpeg sample format unknown.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_specs.rate = (SampleRate) m_codecCtx->sample_rate;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-29 12:08:00 +01:00
|
|
|
FFMPEGReader::FFMPEGReader(const std::string &filename, int stream) :
|
2018-02-01 09:11:04 -02:00
|
|
|
m_pkgbuf(),
|
|
|
|
|
m_formatCtx(nullptr),
|
2018-06-08 23:10:52 +02:00
|
|
|
m_codecCtx(nullptr),
|
|
|
|
|
m_frame(nullptr),
|
2018-02-01 09:11:04 -02:00
|
|
|
m_aviocontext(nullptr),
|
|
|
|
|
m_membuf(nullptr)
|
|
|
|
|
{
|
|
|
|
|
// open file
|
|
|
|
|
if(avformat_open_input(&m_formatCtx, filename.c_str(), nullptr, nullptr)!=0)
|
|
|
|
|
AUD_THROW(FileException, "File couldn't be opened with ffmpeg.");
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2021-08-30 22:36:02 +02:00
|
|
|
init(stream);
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
catch(Exception&)
|
|
|
|
|
{
|
|
|
|
|
avformat_close_input(&m_formatCtx);
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 22:36:02 +02:00
|
|
|
FFMPEGReader::FFMPEGReader(std::shared_ptr<Buffer> buffer, int stream) :
|
2018-02-01 09:11:04 -02:00
|
|
|
m_pkgbuf(),
|
2018-06-08 23:10:52 +02:00
|
|
|
m_codecCtx(nullptr),
|
|
|
|
|
m_frame(nullptr),
|
2018-02-01 09:11:04 -02:00
|
|
|
m_membuffer(buffer),
|
|
|
|
|
m_membufferpos(0)
|
|
|
|
|
{
|
2018-06-08 23:10:52 +02:00
|
|
|
m_membuf = reinterpret_cast<data_t*>(av_malloc(AV_INPUT_BUFFER_MIN_SIZE + AV_INPUT_BUFFER_PADDING_SIZE));
|
2018-02-01 09:11:04 -02:00
|
|
|
|
2018-06-08 23:10:52 +02:00
|
|
|
m_aviocontext = avio_alloc_context(m_membuf, AV_INPUT_BUFFER_MIN_SIZE, 0, this, read_packet, nullptr, seek_packet);
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
if(!m_aviocontext)
|
|
|
|
|
{
|
|
|
|
|
av_free(m_aviocontext);
|
|
|
|
|
AUD_THROW(FileException, "Buffer reading context couldn't be created with ffmpeg.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_formatCtx = avformat_alloc_context();
|
|
|
|
|
m_formatCtx->pb = m_aviocontext;
|
|
|
|
|
if(avformat_open_input(&m_formatCtx, "", nullptr, nullptr)!=0)
|
|
|
|
|
{
|
|
|
|
|
av_free(m_aviocontext);
|
|
|
|
|
AUD_THROW(FileException, "Buffer couldn't be read with ffmpeg.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2021-08-30 22:36:02 +02:00
|
|
|
init(stream);
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
catch(Exception&)
|
|
|
|
|
{
|
|
|
|
|
avformat_close_input(&m_formatCtx);
|
|
|
|
|
av_free(m_aviocontext);
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FFMPEGReader::~FFMPEGReader()
|
|
|
|
|
{
|
2018-06-08 23:10:52 +02:00
|
|
|
if(m_frame)
|
|
|
|
|
av_frame_free(&m_frame);
|
|
|
|
|
#ifdef FFMPEG_OLD_CODE
|
2018-02-01 09:11:04 -02:00
|
|
|
avcodec_close(m_codecCtx);
|
2018-06-08 23:10:52 +02:00
|
|
|
#else
|
|
|
|
|
if(m_codecCtx)
|
|
|
|
|
avcodec_free_context(&m_codecCtx);
|
|
|
|
|
#endif
|
2018-02-01 09:11:04 -02:00
|
|
|
avformat_close_input(&m_formatCtx);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 22:36:02 +02:00
|
|
|
std::vector<StreamInfo> FFMPEGReader::queryStreams()
|
|
|
|
|
{
|
|
|
|
|
std::vector<StreamInfo> result;
|
|
|
|
|
|
|
|
|
|
for(unsigned int i = 0; i < m_formatCtx->nb_streams; i++)
|
|
|
|
|
{
|
|
|
|
|
#ifdef FFMPEG_OLD_CODE
|
|
|
|
|
if(m_formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
|
|
|
#else
|
|
|
|
|
if(m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
StreamInfo info;
|
|
|
|
|
|
|
|
|
|
double time_base = av_q2d(m_formatCtx->streams[i]->time_base);
|
|
|
|
|
|
|
|
|
|
if(m_formatCtx->streams[i]->start_time != AV_NOPTS_VALUE)
|
|
|
|
|
info.start = m_formatCtx->streams[i]->start_time * time_base;
|
|
|
|
|
else
|
|
|
|
|
info.start = 0;
|
|
|
|
|
|
|
|
|
|
if(m_formatCtx->streams[i]->duration != AV_NOPTS_VALUE)
|
|
|
|
|
info.duration = m_formatCtx->streams[i]->duration * time_base;
|
|
|
|
|
else if(m_formatCtx->duration != AV_NOPTS_VALUE)
|
|
|
|
|
info.duration = double(m_formatCtx->duration) / AV_TIME_BASE - info.start;
|
|
|
|
|
else
|
|
|
|
|
info.duration = 0;
|
|
|
|
|
|
|
|
|
|
#ifdef FFMPEG_OLD_CODE
|
|
|
|
|
info.specs.channels = Channels(m_formatCtx->streams[i]->codec->channels);
|
|
|
|
|
info.specs.rate = m_formatCtx->streams[i]->codec->sample_rate;
|
|
|
|
|
info.specs.format = convertSampleFormat(m_formatCtx->streams[i]->codec->sample_fmt);
|
|
|
|
|
#else
|
2024-11-18 17:46:04 +01:00
|
|
|
#ifdef FFMPEG_OLD_CH_LAYOUT
|
|
|
|
|
int channels = m_formatCtx->streams[i]->codecpar->channels;
|
|
|
|
|
#else
|
|
|
|
|
int channels = m_formatCtx->streams[i]->codecpar->ch_layout.nb_channels;
|
|
|
|
|
#endif
|
|
|
|
|
info.specs.channels = Channels(channels);
|
2021-08-30 22:36:02 +02:00
|
|
|
info.specs.rate = m_formatCtx->streams[i]->codecpar->sample_rate;
|
|
|
|
|
info.specs.format = convertSampleFormat(AVSampleFormat(m_formatCtx->streams[i]->codecpar->format));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
result.emplace_back(info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-01 09:11:04 -02:00
|
|
|
int FFMPEGReader::read_packet(void* opaque, uint8_t* buf, int buf_size)
|
|
|
|
|
{
|
|
|
|
|
FFMPEGReader* reader = reinterpret_cast<FFMPEGReader*>(opaque);
|
|
|
|
|
|
2022-04-22 22:36:04 +02:00
|
|
|
long long size = std::min(static_cast<long long>(buf_size), reader->m_membuffer->getSize() - reader->m_membufferpos);
|
2019-11-22 10:34:01 +01:00
|
|
|
|
2022-08-02 20:17:46 +02:00
|
|
|
if(size <= 0)
|
|
|
|
|
return AVERROR_EOF;
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
std::memcpy(buf, ((data_t*)reader->m_membuffer->getBuffer()) + reader->m_membufferpos, size);
|
|
|
|
|
reader->m_membufferpos += size;
|
|
|
|
|
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int64_t FFMPEGReader::seek_packet(void* opaque, int64_t offset, int whence)
|
|
|
|
|
{
|
|
|
|
|
FFMPEGReader* reader = reinterpret_cast<FFMPEGReader*>(opaque);
|
|
|
|
|
|
|
|
|
|
switch(whence)
|
|
|
|
|
{
|
|
|
|
|
case SEEK_SET:
|
|
|
|
|
reader->m_membufferpos = 0;
|
|
|
|
|
break;
|
|
|
|
|
case SEEK_END:
|
|
|
|
|
reader->m_membufferpos = reader->m_membuffer->getSize();
|
|
|
|
|
break;
|
|
|
|
|
case AVSEEK_SIZE:
|
|
|
|
|
return reader->m_membuffer->getSize();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-22 10:34:01 +01:00
|
|
|
int64_t position = reader->m_membufferpos + offset;
|
|
|
|
|
|
|
|
|
|
if(position > reader->m_membuffer->getSize())
|
|
|
|
|
position = reader->m_membuffer->getSize();
|
|
|
|
|
|
|
|
|
|
reader->m_membufferpos = int(position);
|
|
|
|
|
|
|
|
|
|
return position;
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FFMPEGReader::isSeekable() const
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FFMPEGReader::seek(int position)
|
|
|
|
|
{
|
|
|
|
|
if(position >= 0)
|
|
|
|
|
{
|
2021-08-30 22:36:02 +02:00
|
|
|
double pts_time_base = av_q2d(m_formatCtx->streams[m_stream]->time_base);
|
2018-02-01 09:11:04 -02:00
|
|
|
|
Fix #121719: Sound does not play when playback starts outside of strip
The issue was caused by an integer overflow in the `FFMPEGReader::seek`, which
lead to different results depending on an exact compiler version and platform.
The overflow happened in the equation which calculates `m_position`, in its
`packet.pts - st_time` part, which is effectively `int64_t - uint64_t` which is
expected to result in `uint64_t` due to the way how upcasting works in C++.
With the values `packet.pts=0` and `st_time=353600` from the test file the
calculation resulted in a negative value, which then wrapped around due to the
unsigned nature of the type, resulting in very big value on arm64. The value
was wrong, but it did not manifest itself as an error, since the seek code
believed it found the place.
However, when built on x86 platform, the result value was very large negative
value. It is possible that the type upcasting went different, due to the
multiplication with a double value later in the formula. And this large negative
value lead to the seeking code to read the entire file, trying to make it so
the `m_position` is not less than the requested position.
Fortunately, the `AVStream::start_time` is `int64_t`, which leads to a simple
fix which avoids signed/unsigned cast, making the seeking code to properly
calculate `m_position` on both arm64 and x64 platforms, regardless of the
compiler.
Pull Request: https://projects.blender.org/blender/blender/pulls/122149
2024-05-23 21:25:04 +02:00
|
|
|
int64_t st_time = m_formatCtx->streams[m_stream]->start_time;
|
2021-08-30 22:36:02 +02:00
|
|
|
uint64_t seek_pos = (uint64_t)(position / (pts_time_base * m_specs.rate));
|
2018-02-01 09:11:04 -02:00
|
|
|
|
2021-08-30 22:36:02 +02:00
|
|
|
if(st_time != AV_NOPTS_VALUE)
|
|
|
|
|
seek_pos += st_time;
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
// a value < 0 tells us that seeking failed
|
2021-08-30 22:36:02 +02:00
|
|
|
if(av_seek_frame(m_formatCtx, m_stream, seek_pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY) >= 0)
|
2018-02-01 09:11:04 -02:00
|
|
|
{
|
|
|
|
|
avcodec_flush_buffers(m_codecCtx);
|
|
|
|
|
m_position = position;
|
|
|
|
|
|
|
|
|
|
AVPacket packet;
|
|
|
|
|
bool search = true;
|
|
|
|
|
|
|
|
|
|
while(search && av_read_frame(m_formatCtx, &packet) >= 0)
|
|
|
|
|
{
|
|
|
|
|
// is it a frame from the audio stream?
|
|
|
|
|
if(packet.stream_index == m_stream)
|
|
|
|
|
{
|
|
|
|
|
// decode the package
|
|
|
|
|
m_pkgbuf_left = decode(packet, m_pkgbuf);
|
|
|
|
|
search = false;
|
|
|
|
|
|
|
|
|
|
// check position
|
|
|
|
|
if(packet.pts != AV_NOPTS_VALUE)
|
|
|
|
|
{
|
|
|
|
|
// calculate real position, and read to frame!
|
2021-08-30 22:36:02 +02:00
|
|
|
m_position = (packet.pts - (st_time != AV_NOPTS_VALUE ? st_time : 0)) * pts_time_base * m_specs.rate;
|
2018-02-01 09:11:04 -02:00
|
|
|
|
|
|
|
|
if(m_position < position)
|
|
|
|
|
{
|
|
|
|
|
// read until we're at the right position
|
|
|
|
|
int length = AUD_DEFAULT_BUFFER_SIZE;
|
|
|
|
|
Buffer buffer(length * AUD_SAMPLE_SIZE(m_specs));
|
|
|
|
|
bool eos;
|
|
|
|
|
for(int len = position - m_position; len > 0; len -= AUD_DEFAULT_BUFFER_SIZE)
|
|
|
|
|
{
|
|
|
|
|
if(len < AUD_DEFAULT_BUFFER_SIZE)
|
|
|
|
|
length = len;
|
|
|
|
|
read(length, eos, buffer.getBuffer());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-08 23:10:52 +02:00
|
|
|
av_packet_unref(&packet);
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "seeking failed!\n");
|
|
|
|
|
// Seeking failed, do nothing.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FFMPEGReader::getLength() const
|
|
|
|
|
{
|
2021-08-30 22:36:02 +02:00
|
|
|
auto stream = m_formatCtx->streams[m_stream];
|
|
|
|
|
|
|
|
|
|
double time_base = av_q2d(stream->time_base);
|
|
|
|
|
double duration;
|
|
|
|
|
|
|
|
|
|
if(stream->duration != AV_NOPTS_VALUE)
|
|
|
|
|
duration = stream->duration * time_base;
|
|
|
|
|
else if(m_formatCtx->duration != AV_NOPTS_VALUE)
|
|
|
|
|
{
|
|
|
|
|
duration = float(m_formatCtx->duration) / AV_TIME_BASE;
|
|
|
|
|
|
|
|
|
|
if(stream->start_time != AV_NOPTS_VALUE)
|
|
|
|
|
duration -= stream->start_time * time_base;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
duration = -1;
|
|
|
|
|
|
2018-02-01 09:11:04 -02:00
|
|
|
// return approximated remaning size
|
2021-08-30 22:36:02 +02:00
|
|
|
return (int)(duration * m_codecCtx->sample_rate) - m_position;
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FFMPEGReader::getPosition() const
|
|
|
|
|
{
|
|
|
|
|
return m_position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Specs FFMPEGReader::getSpecs() const
|
|
|
|
|
{
|
|
|
|
|
return m_specs.specs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FFMPEGReader::read(int& length, bool& eos, sample_t* buffer)
|
|
|
|
|
{
|
|
|
|
|
// read packages and decode them
|
2018-06-08 23:10:52 +02:00
|
|
|
AVPacket packet = {};
|
2018-02-01 09:11:04 -02:00
|
|
|
int data_size = 0;
|
|
|
|
|
int pkgbuf_pos;
|
|
|
|
|
int left = length;
|
|
|
|
|
int sample_size = AUD_DEVICE_SAMPLE_SIZE(m_specs);
|
|
|
|
|
|
|
|
|
|
sample_t* buf = buffer;
|
|
|
|
|
pkgbuf_pos = m_pkgbuf_left;
|
|
|
|
|
m_pkgbuf_left = 0;
|
|
|
|
|
|
|
|
|
|
// there may still be data in the buffer from the last call
|
|
|
|
|
if(pkgbuf_pos > 0)
|
|
|
|
|
{
|
|
|
|
|
data_size = std::min(pkgbuf_pos, left * sample_size);
|
|
|
|
|
m_convert((data_t*) buf, (data_t*) m_pkgbuf.getBuffer(), data_size / AUD_FORMAT_SIZE(m_specs.format));
|
|
|
|
|
buf += data_size / AUD_FORMAT_SIZE(m_specs.format);
|
2018-06-08 23:10:52 +02:00
|
|
|
left -= data_size / sample_size;
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// for each frame read as long as there isn't enough data already
|
|
|
|
|
while((left > 0) && (av_read_frame(m_formatCtx, &packet) >= 0))
|
|
|
|
|
{
|
|
|
|
|
// is it a frame from the audio stream?
|
|
|
|
|
if(packet.stream_index == m_stream)
|
|
|
|
|
{
|
|
|
|
|
// decode the package
|
|
|
|
|
pkgbuf_pos = decode(packet, m_pkgbuf);
|
|
|
|
|
|
2021-08-30 22:36:02 +02:00
|
|
|
// copy to output buffer
|
|
|
|
|
data_size = std::min(pkgbuf_pos, left * sample_size);
|
|
|
|
|
m_convert((data_t*) buf, (data_t*) m_pkgbuf.getBuffer(), data_size / AUD_FORMAT_SIZE(m_specs.format));
|
|
|
|
|
buf += data_size / AUD_FORMAT_SIZE(m_specs.format);
|
|
|
|
|
left -= data_size / sample_size;
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
2018-06-08 23:10:52 +02:00
|
|
|
av_packet_unref(&packet);
|
2018-02-01 09:11:04 -02:00
|
|
|
}
|
|
|
|
|
// read more data than necessary?
|
|
|
|
|
if(pkgbuf_pos > data_size)
|
|
|
|
|
{
|
|
|
|
|
m_pkgbuf_left = pkgbuf_pos-data_size;
|
|
|
|
|
memmove(m_pkgbuf.getBuffer(),
|
|
|
|
|
((data_t*)m_pkgbuf.getBuffer())+data_size,
|
|
|
|
|
pkgbuf_pos-data_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if((eos = (left > 0)))
|
|
|
|
|
length -= left;
|
|
|
|
|
|
|
|
|
|
m_position += length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AUD_NAMESPACE_END
|