Введение
Qt Multimedia предоставляет высокоуровневый, удобный API для типичных задач воспроизведения и записи — но для более специфичных, продвинутых задач (произвольное транскодирование, статья 473; сложная, кастомная обработка видеокадров; поддержка форматов, не покрытых стандартным API) часто используется FFmpeg — мощная, широко распространённая библиотека (и одноимённая консольная утилита) для обработки мультимедиа, либо как внешний процесс, либо через прямую интеграцию её библиотек (libavcodec, libavformat) в код приложения.
Концепция
Использование FFmpeg как внешнего процесса (через QProcess) — простой, но менее интегрированный подход, где приложение запускает консольную утилиту ffmpeg с нужными параметрами командной строки для выполнения конкретной задачи (транскодирование, извлечение кадров, наложение фильтров) и обрабатывает результат после завершения процесса. Прямая интеграция библиотек FFmpeg (libavcodec для кодеков, libavformat для контейнерных форматов) в код C++/Qt-приложения даёт значительно более глубокий, программный контроль (доступ к отдельным декодированным кадрам, кастомная обработка на уровне отдельных пакетов данных), но требует существенно большей сложности кода и понимания низкоуровневого API самого FFmpeg.
Пример кода
// Использование FFmpeg как внешнего процесса для транскодирования — простой, но эффективный подход
#include <QProcess>
void transcodeVideo(const QString &inputPath, const QString &outputPath,
std::function<void(bool success)> onFinished)
{
auto *process = new QProcess();
QStringList arguments = {
"-i", inputPath,
"-c:v", "libx264", // видеокодек H.264
"-preset", "medium", // баланс скорости кодирования и сжатия
"-c:a", "aac", // аудиокодек AAC
"-y", // перезаписать выходной файл без запроса подтверждения
outputPath
};
QObject::connect(process, &QProcess::finished,
[process, onFinished](int exitCode, QProcess::ExitStatus) {
onFinished(exitCode == 0);
process->deleteLater();
});
process->start("ffmpeg", arguments);
}
// Извлечение прогресса транскодирования из вывода FFmpeg (парсинг stderr, куда ffmpeg пишет прогресс)
void transcodeWithProgress(QProcess *process, std::function<void(int percent)> onProgress, qint64 totalDurationMs)
{
QObject::connect(process, &QProcess::readyReadStandardError, [=]() {
QString output = QString::fromUtf8(process->readAllStandardError());
QRegularExpression timeRegex(R"(time=(d+):(d+):(d+).(d+))");
QRegularExpressionMatch match = timeRegex.match(output);
if (match.hasMatch()) {
qint64 currentMs = match.captured(1).toInt() * 3600000
+ match.captured(2).toInt() * 60000
+ match.captured(3).toInt() * 1000;
int percent = static_cast<int>((currentMs * 100) / totalDurationMs);
onProgress(percent);
}
});
}
Пояснения к коду
transcodeVideo показывает простой, но практичный подход — формирование аргументов командной строки ffmpeg, эквивалентных тому, что выполнил бы пользователь вручную в терминале, и запуск через QProcess с обработкой завершения через сигнал finished, что не требует прямой интеграции сложных библиотек FFmpeg, но достаточно для большинства задач пакетного транскодирования. transcodeWithProgress демонстрирует менее очевидный, но практически важный приём — FFmpeg выводит прогресс выполнения в stderr (а не stdout) в текстовом, парсируемом формате, включающем текущее обработанное время (time=HH:MM:SS.ms), и регулярное выражение позволяет извлечь эту информацию для построения индикатора прогресса длительной операции транскодирования, не предоставляемого напрямую самим FFmpeg в более удобной, структурированной форме.
Подводные камни
- Зависимость от наличия установленного во внешней системе исполняемого файла FFmpeg, не входящего в состав самого Qt и требующего отдельной установки или включения в дистрибутив приложения — приложение, использующее FFmpeg как внешний процесс, должно либо явно проверять его наличие и версию перед использованием, либо включать собственную, статически слинкованную или поставляемую вместе с приложением копию исполняемого файла, без чего попытка запуска
ffmpegна системе без его установки завершится ошибкой запуска процесса. - Парсинг текстового вывода FFmpeg (как в примере с прогрессом) без учёта возможных изменений точного формата этого вывода между разными версиями FFmpeg — формат текстового вывода консольной утилиты не является строго стабильным, документированным API, и обновление версии FFmpeg теоретически может изменить точный формат строк прогресса, что нарушит парсинг через жёстко заданное регулярное выражение; для критичных к стабильности случаев интеграции стоит рассмотреть более стабильные способы получения прогресса (если доступны) или быть готовым к необходимости адаптации парсинга при обновлении используемой версии FFmpeg.
- Игнорирование лицензионных особенностей FFmpeg и отдельных входящих в его состав кодеков для коммерческого распространения приложения — сам FFmpeg распространяется под лицензиями LGPL/GPL (в зависимости от конкретной сборки и включённых компонентов), и отдельные кодеки внутри него могут иметь дополнительные патентные ограничения (аналогично подводному камню статьи 473 про H.264) — перед включением FFmpeg в коммерческий продукт стоит внимательно изучить применимые лицензионные условия конкретной используемой сборки.
- Прямая интеграция низкоуровневых библиотек FFmpeg (libavcodec/libavformat) без достаточного понимания их сложного, низкоуровневого C-API, спроектированного без учёта удобства использования из современного C++ — этот API требует ручного управления памятью пакетов и кадров, явной обработки множества предельных случаев формата данных, и ошибки в этой интеграции (утечки памяти, некорректное освобождение ресурсов) особенно вероятны для разработчиков без специфичного опыта именно с этим конкретным, нетривиальным API; для большинства практических задач использование FFmpeg как внешнего процесса (первый пример статьи) — значительно более простой и менее подверженный ошибкам путь.