Видео не сжато с помощью ffmpeg

Вопрос или проблема

Я написал обертку для 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 заключается в нескольких ключевых моментах, которые необходимо учесть. Давайте рассмотрим, что именно может быть неправильно и как это исправить.

Анализ проблемы:

  1. Кодек и параметры кодирования: В вашем коде, при создании нового потока вывода, вы настроили AVCodecContext, но не инициализировали его должным образом. Вам нужно настроить ключевые параметры кодека, такие как битрейт, использование кодека, параметры crf (Constant Rate Factor) и другие, до начала кодирования.

  2. Кодирование пакетов: Вы не вызываете функцию avcodec_open2 после инициализации кодека, что необходимо для успешного кодирования. Без этого кодек не сможет обрабатывать кадры.

  3. Поток инициализации: Убедитесь, что каждый поток настроен правильно, и параметры кодирования передаются в AVStream после того, как кодек открыт.

  4. Вызовы методов: Ваша функция 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;
        }
    }
}

Общие рекомендации:

  1. Проверка ошибок: Включите дополнительные проверки на наличие ошибок после каждого вызова FFmpeg. Это поможет вам лучше понять, где именно происходит сбой.

  2. Логи: Поскольку у вас уже настроены логи, убедитесь, что вы внимательно просматриваете выходные данные на наличие предупреждений или ошибок, связанных с энкодингом, особенно когда вы вызываете av_read_frame и av_interleaved_write_frame.

  3. Флаги движения: Убедитесь, что флаг AVFMT_GLOBALHEADER установлен, если вы используете его, чтобы избежать проблем с кодированием.

  4. Примеры: Рассмотрите возможность запуска простых примеров с одним видеофайлом с помощью командной строки FFmpeg чтобы удостовериться, что ваши команды работают правильно вне вашего кода.

Следуя этим рекомендациям и дополнительному коду, вы должны быть на пути к успешной компрессии видео с помощью FFmpeg в вашем проекте JNI.

Оцените материал
Добавить комментарий

Капча загружается...