Skip to content
Open
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
23 changes: 23 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,27 @@ set(CMAKE_AUTOMOC ON)
# Ищем нужные модули Qt
find_package(Qt6 REQUIRED COMPONENTS Widgets)

#ffmpeg
#Самый просто способ через pkg-config
find_package(PkgConfig REQUIRED)

pkg_check_modules(AVCODEC REQUIRED libavcodec)
pkg_check_modules(AVFORMAT REQUIRED libavformat)
pkg_check_modules(AVUTIL REQUIRED libavutil)
pkg_check_modules(SWRESAMPLE REQUIRED libswresample)

include_directories(
${AVCODEC_INCLUDE_DIRS}
${AVFORMAT_INCLUDE_DIRS}
${AVUTIL_INCLUDE_DIRS}
${SWRESAMPLE_INCLUDE_DIRS}
)

link_directories(
${AVCODEC_LIBRARY_DIRS}
${AVFORMAT_LIBRARY_DIRS}
${AVUTIL_LIBRARY_DIRS}
${SWRESAMPLE_LIBRARY_DIRS}
)

add_subdirectory(src)
158 changes: 158 additions & 0 deletions src/AudioDecoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#include "AudioDecoder.hpp"
#include <QDebug>

AudioDecoder::AudioDecoder() = default;

AudioDecoder::~AudioDecoder() {
close();
}

bool AudioDecoder::open(const QString& inputPath) {
m_inputPath = inputPath;

if (avformat_open_input(&m_formatContext, m_inputPath.toUtf8().constData(), nullptr, nullptr) < 0) {
qWarning() << "ERROR: Failed to open input file";
return false;
}
if (avformat_find_stream_info(m_formatContext, nullptr) < 0) {
qWarning() << "ERROR: Failed to find stream info";
return false;
}

m_streamIndex = av_find_best_stream(m_formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (m_streamIndex < 0) {
qWarning() << "ERROR: No audio stream found";
return false;
}

AVStream* stream = m_formatContext->streams[m_streamIndex];
const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!codec) {
qWarning() << "ERROR: Decoder not found";
return false;
}

m_codecContext = avcodec_alloc_context3(codec);
if (!m_codecContext || avcodec_parameters_to_context(m_codecContext, stream->codecpar) < 0) {
qWarning() << "ERROR: Failed to copy codec parameters";
return false;
}
if (avcodec_open2(m_codecContext, codec, nullptr) < 0) {
qWarning() << "ERROR: Failed to open codec";
return false;
}

// ensure input layout is set
if (m_codecContext->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) {
av_channel_layout_default(&m_codecContext->ch_layout, m_codecContext->ch_layout.nb_channels);
}
av_channel_layout_copy(&m_inputChannelLayout, &m_codecContext->ch_layout);

// save output params and init default layout
m_outputSampleRate = m_codecContext->sample_rate;
m_outputChannels = m_codecContext->ch_layout.nb_channels;
av_channel_layout_default(&m_outputChannelLayout, m_outputChannels);

if (!initResampler())
return false;

m_packet = av_packet_alloc();
m_frame = av_frame_alloc();
m_endOfFile = false;

return true;
}

bool AudioDecoder::initResampler() {
if (!m_codecContext)
return false;

if (m_swrContext)
swr_free(&m_swrContext);

if (swr_alloc_set_opts2(&m_swrContext,
&m_outputChannelLayout,
m_outputSampleFormat,
m_outputSampleRate,
&m_inputChannelLayout,
m_codecContext->sample_fmt,
m_codecContext->sample_rate,
0, nullptr) < 0) {
qWarning() << "ERROR: Failed to allocate resampler";
return false;
}
if (swr_init(m_swrContext) < 0) {
qWarning() << "ERROR: Failed to initialize resampler";
return false;
}
return true;
}

int AudioDecoder::decode(uint8_t* outputBuffer, int outputBufferSize) {
if (!m_formatContext || !m_codecContext || !m_swrContext)
return -1;

while (true) {
if (!m_endOfFile) {
if (av_read_frame(m_formatContext, m_packet) < 0) {
m_endOfFile = true;
avcodec_send_packet(m_codecContext, nullptr);
} else if (m_packet->stream_index == m_streamIndex) {
avcodec_send_packet(m_codecContext, m_packet);
av_packet_unref(m_packet);
} else {
av_packet_unref(m_packet);
continue;
}
}

int ret = avcodec_receive_frame(m_codecContext, m_frame);
if (ret == AVERROR(EAGAIN))
continue;
if (ret == AVERROR_EOF)
return 0;
if (ret < 0) {
qWarning() << "ERROR: Failed to receive frame";
return -1;
}

// resample into outputBuffer
int maxSamples = outputBufferSize / av_get_bytes_per_sample(m_outputSampleFormat);
int samplesConverted = swr_convert(m_swrContext,
&outputBuffer,
maxSamples,
(const uint8_t**)m_frame->data,
m_frame->nb_samples);
av_frame_unref(m_frame);
if (samplesConverted < 0) {
qWarning() << "ERROR: Failed to resample";
return -1;
}
return samplesConverted * m_outputChannels * av_get_bytes_per_sample(m_outputSampleFormat);
}
}

void AudioDecoder::close() {
if (m_frame) av_frame_free(&m_frame);
if (m_packet) av_packet_free(&m_packet);
if (m_swrContext) swr_free(&m_swrContext);
if (m_codecContext) avcodec_free_context(&m_codecContext);
if (m_formatContext) avformat_close_input(&m_formatContext);

av_channel_layout_uninit(&m_inputChannelLayout);
av_channel_layout_uninit(&m_outputChannelLayout);
}

int AudioDecoder::bitrate() const {
return m_formatContext ? m_formatContext->bit_rate : 0;
}

qint64 AudioDecoder::durationUs() const {
return (m_formatContext && m_formatContext->duration != AV_NOPTS_VALUE)
? m_formatContext->duration
: 0;
}

QString AudioDecoder::sampleFormatName() const {
return QString::fromUtf8(av_get_sample_fmt_name(m_outputSampleFormat));
}
47 changes: 47 additions & 0 deletions src/AudioDecoder.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include <QString>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/samplefmt.h>
#include <libavutil/channel_layout.h>
#include <libswresample/swresample.h>
}

class AudioDecoder {
public:
AudioDecoder();
~AudioDecoder();

bool open(const QString& inputPath);
int decode(uint8_t* outputBuffer, int outputBufferSize);
void close();

int sampleRate() const { return m_outputSampleRate; }
int channels() const { return m_outputChannels; }
int bytesPerSample() const { return av_get_bytes_per_sample(m_outputSampleFormat); }

int bitrate() const;
qint64 durationUs() const;
QString sampleFormatName() const;

private:
bool initResampler();

QString m_inputPath;
AVFormatContext* m_formatContext = nullptr;
AVCodecContext* m_codecContext = nullptr;
SwrContext* m_swrContext = nullptr;
AVPacket* m_packet = nullptr;
AVFrame* m_frame = nullptr;

int m_streamIndex = -1;
AVChannelLayout m_inputChannelLayout;
AVChannelLayout m_outputChannelLayout;
AVSampleFormat m_outputSampleFormat = AV_SAMPLE_FMT_S16;
int m_outputSampleRate = 0;
int m_outputChannels = 0;
bool m_endOfFile = false;
};
96 changes: 96 additions & 0 deletions src/AudioStreamBuffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include "AudioStreamBuffer.hpp"
#include <cstring> // memcpy

AudioStreamBuffer::AudioStreamBuffer(AudioDecoder& decoder, double bufferDurationSec)
: m_decoder(decoder)
{
m_frameSize = m_decoder.channels() * m_decoder.bytesPerSample();
m_bufferSize = static_cast<size_t>(m_decoder.sampleRate() * bufferDurationSec * m_frameSize);
m_buffer.resize(m_bufferSize, 0);
}

bool AudioStreamBuffer::fill() {
if (m_eof) return false;

uint8_t temp[8192]; // временный маленький буфер для одного чтения

while (availableSamples() < m_bufferSize / 2) {
int bytesRead = m_decoder.decode(temp, sizeof(temp));
if (bytesRead > 0) {
writeToBuffer(temp, static_cast<size_t>(bytesRead));
} else {
m_eof = true;
break;
}
}
return true;
}

bool AudioStreamBuffer::getSamples(uint8_t* outBuffer, size_t bytesToRead) {
if (isEmpty()) return false;

size_t bytesAvailable = availableSamples();
size_t bytesToCopy = (bytesToRead < bytesAvailable) ? bytesToRead : bytesAvailable;

if (m_readPos + bytesToCopy <= m_bufferSize) {
std::memcpy(outBuffer, &m_buffer[m_readPos], bytesToCopy);
m_readPos += bytesToCopy;
} else {
size_t firstPart = m_bufferSize - m_readPos;
size_t secondPart = bytesToCopy - firstPart;
std::memcpy(outBuffer, &m_buffer[m_readPos], firstPart);
std::memcpy(outBuffer + firstPart, &m_buffer[0], secondPart);
m_readPos = secondPart;
}

if (m_readPos >= m_bufferSize) m_readPos = 0;
return true;
}

bool AudioStreamBuffer::isEmpty() const {
return m_readPos == m_writePos;
}

bool AudioStreamBuffer::isEof() const {
return m_eof;
}

int AudioStreamBuffer::sampleRate() const {
return m_decoder.sampleRate();
}

int AudioStreamBuffer::channels() const {
return m_decoder.channels();
}

int AudioStreamBuffer::bytesPerSample() const {
return m_decoder.bytesPerSample();
}

void AudioStreamBuffer::writeToBuffer(const uint8_t* data, size_t size) {
if (size > m_bufferSize) {
// слишком большой кусок, обрезаем
data += size - m_bufferSize;
size = m_bufferSize;
}

if (m_writePos + size <= m_bufferSize) {
std::memcpy(&m_buffer[m_writePos], data, size);
m_writePos += size;
} else {
size_t firstPart = m_bufferSize - m_writePos;
size_t secondPart = size - firstPart;
std::memcpy(&m_buffer[m_writePos], data, firstPart);
std::memcpy(&m_buffer[0], data + firstPart, secondPart);
m_writePos = secondPart;
}

if (m_writePos >= m_bufferSize) m_writePos = 0;
}

size_t AudioStreamBuffer::availableSamples() const {
if (m_writePos >= m_readPos)
return m_writePos - m_readPos;
else
return m_bufferSize - (m_readPos - m_writePos);
}
34 changes: 34 additions & 0 deletions src/AudioStreamBuffer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include "AudioDecoder.hpp"
#include <vector>
#include <cstdint>

class AudioStreamBuffer {
public:
explicit AudioStreamBuffer(AudioDecoder& decoder, double bufferDurationSec = 2.0);

bool fill(); // наполняет буфер новыми данными
bool getSamples(uint8_t* outBuffer, size_t bytesToRead); // копирует samples в outBuffer

bool isEmpty() const;
bool isEof() const;

int sampleRate() const;
int channels() const;
int bytesPerSample() const;

private:
AudioDecoder& m_decoder;

std::vector<uint8_t> m_buffer;
size_t m_bufferSize = 0;
size_t m_readPos = 0;
size_t m_writePos = 0;
bool m_eof = false;

size_t m_frameSize = 0; // bytes per frame (channels × bytes_per_sample)

void writeToBuffer(const uint8_t* data, size_t size);
size_t availableSamples() const;
};
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
endif()

target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets
avformat avcodec avutil swresample)
Loading