Вопрос или проблема
Я написал обертку для JNI на языке C++, чтобы сжать видео. Но сжатие не произошло, и выходное видео точно такое же, как входное видео.
Я хочу выполнить команду ниже:
ffmpeg -i input.mp4 -vcodec libx264 -crf 30 -movflags +faststart output.mp4
#include <iostream>
#include <android/log.h>
#include <jni.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/log.h>
#include <libavutil/time.h>
#include <libswscale/swscale.h>
}
using namespace std;
#define LOG_TAG "VideoNative"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define FFMPEG_LOG_TAG "FFMPEG_VideoNative"
#define FLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, FFMPEG_LOG_TAG, __VA_ARGS__)
#define FLOGI(...) __android_log_print(ANDROID_LOG_INFO, FFMPEG_LOG_TAG, __VA_ARGS__)
#define FLOGE(...) __android_log_print(ANDROID_LOG_ERROR, FFMPEG_LOG_TAG, __VA_ARGS__)
AVFormatContext *initInputFormatContext(const char *path);
AVFormatContext *initOutputFormatContext(const char *path);
void copyAvFormatContext(AVFormatContext **inputContext, AVFormatContext **outputContext);
void writeFileHeader(AVFormatContext *formatContext);
static void ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vargs) {
if (level <= av_log_get_level()) {
char log_buffer[1024];
vsnprintf(log_buffer, sizeof(log_buffer), fmt, vargs);
switch (level) {
case AV_LOG_DEBUG:
FLOGD("%s", log_buffer);
break;
case AV_LOG_WARNING:
case AV_LOG_ERROR:
case AV_LOG_FATAL:
FLOGE("%s", log_buffer);
break;
default:
FLOGI("%s", log_buffer);
break;
}
}
}
void compress(const char *inputPath, const char *outputPath) {
av_register_all();
avcodec_register_all();
avformat_network_init();
av_log_set_level(AV_LOG_TRACE);
av_log_set_callback(ffmpeg_log_callback);
AVFormatContext *inputFormatContext = initInputFormatContext(inputPath);
if (!inputFormatContext) {
LOGE("ошибка контекста формата входа");
return;
}
AVFormatContext *outputFormatContext = initOutputFormatContext(outputPath);
if (!outputFormatContext) {
LOGE("ошибка контекста формата выхода");
return;
}
copyAvFormatContext(&inputFormatContext, &outputFormatContext);
if (avio_open2(&outputFormatContext->pb, outputPath, AVIO_FLAG_WRITE, nullptr, nullptr) < 0) {
LOGE("ошибка открытия файла");
}
writeFileHeader(outputFormatContext);
AVPacket *inputPaket = av_packet_alloc();
if (!inputPaket) {
LOGE("пакет не найден");
}
while (av_read_frame(inputFormatContext, inputPaket) >= 0) {
if (av_interleaved_write_frame(outputFormatContext, inputPaket) < 0) {
LOGE("не удается записать кадр");
}
}
if (av_write_trailer(outputFormatContext) < 0) {
LOGE("не удается записать трейлер");
}
}
void writeFileHeader(AVFormatContext *formatContext) {
AVDictionary *option = nullptr;
if (avformat_write_header(formatContext, &option) < 0) {
LOGE("ошибка записи заголовка");
return;
}
av_dict_free(&option);
}
void copyAvFormatContext(AVFormatContext **inputContext, AVFormatContext **outputContext) {
int numStream = static_cast<int>((*inputContext)->nb_streams);
for (int i = 0; i < numStream; i++) {
AVStream *inputStream = (*inputContext)->streams[i];
AVCodecParameters *inputCodecParams = inputStream->codecpar;
AVCodec *outputCodec = avcodec_find_encoder(inputCodecParams->codec_id);
AVStream *outputStream = avformat_new_stream(*outputContext, outputCodec);
AVCodecContext *codecContext = avcodec_alloc_context3(outputCodec);
codecContext->codec_id = AV_CODEC_ID_H264;
av_opt_set(codecContext->priv_data, "crf", "30", 0);
av_opt_set(codecContext->priv_data, "movflags", "faststart", 0);
if (avcodec_parameters_from_context(outputStream->codecpar, codecContext) < 0) {
LOGE("не удается установить опцию" );
}
AVCodecParameters *outputCodecParams = outputStream->codecpar;
avcodec_parameters_copy(outputCodecParams, inputCodecParams);
}
}
AVFormatContext *initOutputFormatContext(const char *path) {
AVFormatContext *formatContext = avformat_alloc_context();
if (avformat_alloc_output_context2(&formatContext, nullptr, "mp4", path) < 0) {
LOGE("ошибка открытия выхода");
return nullptr;
}
return formatContext;
}
AVFormatContext *initInputFormatContext(const char *path) {
AVFormatContext *formatContext = avformat_alloc_context();
if (avformat_open_input(&formatContext, path, nullptr, nullptr) != 0) {
LOGE("ошибка открытия входа");
return nullptr;
}
if (avformat_find_stream_info(formatContext, nullptr) < 0) {
LOGE("ошибка потока");
return nullptr;
}
return formatContext;
}
extern "C"
JNIEXPORT jint JNICALL
Java_ir_nasim_core_modules_file_FileNativeLoader_compressFile(JNIEnv *env, jobject thiz,
jstring input_path,
jstring output_path) {
compress(env->GetStringUTFChars(input_path, JNI_FALSE),
env->GetStringUTFChars(output_path, JNI_FALSE));
return 0;
}
Я пробовал примеры кода, но ни один из них не сработал.
Буду признателен, если вы поможете мне решить эту проблему.
Ответ или решение
Проблема с отсутствием сжатия видео при использовании вашей реализации на C++ с FFmpeg заключается в нескольких ключевых моментах, которые необходимо учесть. Давайте рассмотрим, что именно может быть неправильно и как это исправить.
Анализ проблемы:
-
Кодек и параметры кодирования: В вашем коде, при создании нового потока вывода, вы настроили
AVCodecContext
, но не инициализировали его должным образом. Вам нужно настроить ключевые параметры кодека, такие как битрейт, использование кодека, параметрыcrf
(Constant Rate Factor) и другие, до начала кодирования. -
Кодирование пакетов: Вы не вызываете функцию
avcodec_open2
после инициализации кодека, что необходимо для успешного кодирования. Без этого кодек не сможет обрабатывать кадры. -
Поток инициализации: Убедитесь, что каждый поток настроен правильно, и параметры кодирования передаются в
AVStream
после того, как кодек открыт. -
Вызовы методов: Ваша функция
while
для чтения и записи фреймов не учитывает правильную обработку кадров, что также может привести к некорректной обработке.
Возможное решение:
Ниже приведен улучшенный фрагмент вашей функции copyAvFormatContext
, в который добавлена нужная инициализация кодеров и обработка:
void copyAvFormatContext(AVFormatContext **inputContext, AVFormatContext **outputContext) {
int numStream = static_cast<int>((*inputContext)->nb_streams);
for (int i = 0; i < numStream; i++) {
AVStream *inputStream = (*inputContext)->streams[i];
AVCodecParameters *inputCodecParams = inputStream->codecpar;
AVCodec *outputCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!outputCodec) {
LOGE("Codec not found");
return;
}
AVStream *outputStream = avformat_new_stream(*outputContext, outputCodec);
AVCodecContext *codecContext = avcodec_alloc_context3(outputCodec);
codecContext->codec_id = AV_CODEC_ID_H264;
codecContext->width = inputCodecParams->width;
codecContext->height = inputCodecParams->height;
codecContext->time_base = inputStream->time_base;
codecContext->pix_fmt = AV_PIX_FMT_YUV420P; // Убедитесь, что выбран правильный формат
// Настройка CRF
av_opt_set(codecContext->priv_data, "crf", "30", 0);
av_opt_set(codecContext->priv_data, "preset", "medium", 0);
// Открытие кодека
if (avcodec_open2(codecContext, outputCodec, nullptr) < 0) {
LOGE("Could not open codec");
return;
}
if (avcodec_parameters_from_context(outputStream->codecpar, codecContext) < 0) {
LOGE("Can't set codec parameters");
return;
}
}
}
Общие рекомендации:
-
Проверка ошибок: Включите дополнительные проверки на наличие ошибок после каждого вызова FFmpeg. Это поможет вам лучше понять, где именно происходит сбой.
-
Логи: Поскольку у вас уже настроены логи, убедитесь, что вы внимательно просматриваете выходные данные на наличие предупреждений или ошибок, связанных с энкодингом, особенно когда вы вызываете
av_read_frame
иav_interleaved_write_frame
. -
Флаги движения: Убедитесь, что флаг
AVFMT_GLOBALHEADER
установлен, если вы используете его, чтобы избежать проблем с кодированием. -
Примеры: Рассмотрите возможность запуска простых примеров с одним видеофайлом с помощью командной строки FFmpeg чтобы удостовериться, что ваши команды работают правильно вне вашего кода.
Следуя этим рекомендациям и дополнительному коду, вы должны быть на пути к успешной компрессии видео с помощью FFmpeg в вашем проекте JNI.