Sélectionner une révision Git
-
Kubat a rédigé
- Don't show the 'Open properties' action in views when it can be toggled by the QDockWidget's action - Use constexpr things to group all dock features -> easy to view and understand now
Kubat a rédigé- Don't show the 'Open properties' action in views when it can be toggled by the QDockWidget's action - Use constexpr things to group all dock features -> easy to view and understand now
Audio.cc 11,04 Kio
#include "Audio.hh"
#include <QJsonObject>
#include <QJsonArray>
using namespace Vivy;
// AudioContext class implementation
// Create an audio contecxt from a file
AudioContext::AudioContext(const QString &path)
: filePath(path)
{
if (!format)
throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
const std::string filePathStdHolder = filePath.toStdString();
const char *filename = filePathStdHolder.c_str();
AVFormatContext *formatPtr = format.get();
// Get the format from the audio file
if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) {
[[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input
throw std::runtime_error("failed to open file");
}
if (avformat_find_stream_info(formatPtr, nullptr) < 0) {
throw std::runtime_error("failed to get audio stream info");
}
// Populate all the stream indexes
for (uint i = 0; i < format->nb_streams; ++i) {
AVStream *itFormat = format->streams[i];
AVCodecParameters *params = itFormat->codecpar;
AVCodec *streamCodec = avcodec_find_decoder(params->codec_id);
if (streamCodec && streamCodec->type == AVMEDIA_TYPE_AUDIO) {
audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i));
}
}
// Get the default stream
defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO,
-1, // Let AV find one stream
-1, // We don't want related streams
nullptr, 0);
if (defaultStreamIndex < 0) {
qCritical() << "Could not find the best audio stream";
}
qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration
<< "and default stream index" << defaultStreamIndex;
}
// Get a specific audio stream, try to lock the weak pointer. if the index was
// not present the used_count will be 0 and you won't be able to lock it.
AudioContext::StreamWeakPtr
AudioContext::getStream(int index) const noexcept
{
if (index < 0)
return StreamWeakPtr{ spareNullSreamPtr };
uint unsignedIndex = static_cast<uint>(index);
const auto found = audioStreams.find(unsignedIndex);
if (found != audioStreams.end())
return StreamWeakPtr{ *found };
return StreamWeakPtr{ spareNullSreamPtr };
}
// Get the default stream of this audio context. If no default audio stream is
// present you won't be able to lock the weak pointer.
AudioContext::StreamWeakPtr
AudioContext::getDefaultStream() const noexcept
{
if (defaultStreamIndex < 0)
return StreamWeakPtr{ spareNullSreamPtr };
return getStream(defaultStreamIndex);
}
QString
AudioContext::getElementName() const noexcept
{
QFileInfo file(filePath);
return "AudioContext: " + file.baseName();
}
QJsonDocument
AudioContext::getProperties() const noexcept
{
QJsonDocument ret;
QJsonArray streams;
QFileInfo file(filePath);
for (const auto &audioStreamPtr : audioStreams) {
streams.append(audioStreamPtr->getProperties());
}
QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } };
ret.setObject(self);
return ret;
}
// AudioContext::Stream class implementation
// Constructor, need an AVFormat and an AVStream
AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *stream,
int index)
: codecId(stream->codecpar->codec_id)
, codec(streamCodec)
, codecParams(stream->codecpar)
, audioStream(stream)
, streamIndexInAudioContext(index)
, dataFormat(formatPtr)
{
if (codec == nullptr)
throw std::runtime_error("failed to find a decoder for stream");
codecContext.reset(avcodec_alloc_context3(codec));
if (!codecContext)
throw std::runtime_error("failed to allocate codec context");
if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0)
throw std::runtime_error("failed to copy parameters to codec context");
if (avcodec_open2(codecContext.get(), codec, nullptr) < 0)
throw std::runtime_error("failed to open audio decoder for a stream");
SwrContext *s = dataSwrContext.get();
av_opt_set_int(s, "in_channel_count", codecContext->channels, 0);
av_opt_set_int(s, "out_channel_count", 1, 0);
av_opt_set_int(s, "in_channel_layout", static_cast<int64_t>(codecContext->channel_layout), 0);
av_opt_set_int(s, "out_channel_layout", AV_CH_LAYOUT_MONO, 0);
av_opt_set_int(s, "in_sample_rate", codecContext->sample_rate, 0);
av_opt_set_int(s, "out_sample_rate", resamplerSampleRate, 0);
av_opt_set_sample_fmt(s, "in_sample_fmt", codecContext->sample_fmt, 0);
av_opt_set_sample_fmt(s, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0);
swr_init(s);
if (!swr_is_initialized(s))
throw std::runtime_error("failed to initialize SwrContext resampler");
qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId;
qDebug() << "[Stream] sample rate:" << codecParams->sample_rate;
qDebug() << "[Stream] bit rate: " << codecParams->bit_rate;
qDebug() << "[Stream] channels: " << codecParams->channels;
}
AudioContext::Stream::~Stream() noexcept
{
if (dataPtr) {
qDebug() << "Free data ptr";
free(dataPtr);
}
qDebug() << "Delete stream object";
}
QJsonObject
AudioContext::Stream::getProperties() const noexcept
{
QJsonObject ret;
ret.insert("Codec name", codec->name);
ret.insert("Sample rate", codecParams->sample_rate);
ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate));
ret.insert("Channels", codecParams->channels);
return ret;
}
// Decode the data, don't call it if data already decoded
void
AudioContext::Stream::decodeData()
{
if (isDecoded())
throw std::logic_error("audio stream is already resampled");
qDebug() << "Launch decoding of stream" << streamIndexInAudioContext;
AVPacket packet;
av_init_packet(&packet);
// Iterate through frames
while (av_read_frame(dataFormat, &packet) >= 0) {
// Only decode audio
if (packet.stream_index != streamIndexInAudioContext) {
av_packet_unref(&packet);
continue;
}
// Decode one frame
int response = avcodec_send_packet(codecContext.get(), &packet);
if (response < 0) [[unlikely]] {
throw std::runtime_error(
QStringLiteral("error n°%1 while sending a packet to the decoder")
.arg(response)
.toStdString());
}
double *buffer = nullptr;
int oldFrameNbSamples = 0;
while (response >= 0) {
response = avcodec_receive_frame(codecContext.get(), dataFrame.get());
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) [[unlikely]] {
break;
} else if (response < 0) [[unlikely]] {
throw std::runtime_error(
QStringLiteral("error n°%1 while receiving a from from the decoder")
.arg(response)
.toStdString());
}
// Reuse buffer if possible
if (oldFrameNbSamples < dataFrame->nb_samples) {
dataDeleter(buffer);
oldFrameNbSamples = dataFrame->nb_samples;
av_samples_alloc(reinterpret_cast<uint8_t **>(&buffer), nullptr, 1,
dataFrame->nb_samples, AV_SAMPLE_FMT_DBL, 0);
}
// Resample frame
if (const int frame_count_int =
swr_convert(dataSwrContext.get(), reinterpret_cast<uint8_t **>(&buffer),
dataFrame->nb_samples, (const uint8_t **)dataFrame->data,
dataFrame->nb_samples);
frame_count_int < 0) [[unlikely]] {
throw std::runtime_error("error on frame count, is negative but should not be");
}
// Append resampled frames to data
else [[likely]] {
const size_t frame_count = static_cast<size_t>(frame_count_int);
dataPtr = reinterpret_cast<double *>(
realloc(dataPtr, (dataSize + (size_t)dataFrame->nb_samples) * sizeof(double)));
memcpy(dataPtr + dataSize, buffer, frame_count * sizeof(double));
dataSize += frame_count;
}
}
dataDeleter(buffer);
av_packet_unref(&packet);
}
qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext
<< "dataPtr =" << dataPtr << "with dataSize =" << dataSize;
}
// Delete decoded data, clean up thing
void
AudioContext::Stream::cleanUpData() noexcept
{
free(dataPtr);
dataPtr = nullptr;
dataSize = 0;
}
// Get the number of channels in the audio context stream
int
AudioContext::Stream::getChannels() const noexcept
{
return codecContext->channels;
}
// Get the sample rate
int
AudioContext::Stream::getSampleRate() const noexcept
{
return codecContext->sample_rate;
}
// Get the sample rate of the decoded data (need to call decodeData for that
// value to have a meaning).
int
AudioContext::Stream::getDataSampleRate() const noexcept
{
return resamplerSampleRate;
}
// Get the name of the codec used to decode the stream
QString
AudioContext::Stream::getName() const noexcept
{
return QString(codec->name);
}
// Get the codec's ID used to decode the stream
AVCodecID
AudioContext::Stream::getCodecId() const noexcept
{
return codecId;
}
// Get the bit rate of the stream
qint64
AudioContext::Stream::getBitRate() const noexcept
{
return codecContext->bit_rate;
}
quint64
AudioContext::Stream::getLength() const noexcept
{
// The whole file duration, not individual streams.
return quint64(std::chrono::duration<qreal,std::ratio<1,1000>>(std::chrono::microseconds(dataFormat->duration)).count());
}
// Get the information about the decoded state of this stream
bool
AudioContext::Stream::isDecoded() const noexcept
{
return dataPtr != nullptr;
}
// Get the decoded data's size, will return 0 if the data is not decoded. Don't
// rely on this behaviour to get the decoded state of the stream!
size_t
AudioContext::Stream::getDecodedDataSize() const noexcept
{
return dataSize;
}
// Get the decoded data, safe to call even if the data is not decoded has the
// pointer will be null. You must check it with an `if` statement.
double *
AudioContext::Stream::getDecodedData() const noexcept
{
return dataPtr;
}
// Get the chunk size of the decoded data
size_t
AudioContext::Stream::getDecodedChunkSize() const noexcept
{
return 512;
}
// Get the decalage of the decoded data
size_t
AudioContext::Stream::getDecodedDecalage() const noexcept
{
constexpr size_t overlap = 128; // The overlap
return getDecodedChunkSize() - overlap;
}
int
AudioContext::Stream::getStreamIndex() const noexcept
{
return streamIndexInAudioContext;
}