Капсуляция сырых кадров opus в ogg с использованием ffmpeg.autogen

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

Мне нужно инкапсулировать необработанные фреймы opus (из формата, аналогичного rtpdump), но у меня возникли проблемы с функцией ffmpeg.avformat_write_header – Ошибка при записи заголовков opus -1094995529

public unsafe class OpusMuxer
{
    public static void ConvertRawOpusToOggOpus(string inputPath, string outputPath, int sampleRate = 48000, int channels = 2)
    {
        ffmpeg.av_log_set_level(ffmpeg.AV_LOG_INFO);
        AVOutputFormat* fmt = ffmpeg.av_guess_format("ogg", null, null);
        if (fmt == null)
        {
            Console.WriteLine("Не удалось определить выходной формат (ogg).");
            return;
        }

        AVFormatContext* oc = null;
        ffmpeg.avformat_alloc_output_context2(&oc, fmt, null, null);
        if (oc == null)
        {
            Console.WriteLine("Не удалось создать контекст выходного формата.");
            return;
        }

        
        AVStream* st = ffmpeg.avformat_new_stream(oc, null);
        if (st == null)
        {
            Console.WriteLine("Не удалось создать новый поток.");
            goto cleanup;
        }

        AVCodecParameters* codecpar = st->codecpar;
        codecpar->codec_type = AVMediaType.AVMEDIA_TYPE_AUDIO;
        codecpar->codec_id = AVCodecID.AV_CODEC_ID_OPUS;
        codecpar->format = 8;//ffmpeg.AV_SAMPLE_FMT_FLTP;
        codecpar->sample_rate = sampleRate;
        ffmpeg.av_channel_layout_from_mask(&st->codecpar->ch_layout, ffmpeg.AV_CH_LAYOUT_STEREO);
        codecpar->ch_layout.nb_channels = channels;

        st->time_base = new AVRational { num = 1, den = codecpar->sample_rate };

        byte[] opusHeader = new byte[] {
        (byte)'O', (byte)'p', (byte)'u', (byte)'s', // Магическая подпись "Opus"
        1, // Версия
        2, // Количество каналов
        0, 0, // Предварительный пропуск
        0x80, 0xBB, 0, 0, // Оригинальная частота дискретизации (48000 Гц)
        0, // Уровень громкости
        0 // Сопоставление каналов
        };

        codecpar->extradata = (byte*)ffmpeg.av_malloc((ulong)opusHeader.Length);
        Marshal.Copy(opusHeader, 0, (IntPtr)codecpar->extradata, opusHeader.Length);
        codecpar->extradata_size = opusHeader.Length;


     
        if ((fmt->flags & ffmpeg.AVFMT_NOFILE) == 0)
        {
            int ret = ffmpeg.avio_open(&oc->pb, outputPath, ffmpeg.AVIO_FLAG_WRITE);
            if (ret < 0)
            {
                Console.WriteLine("Не удалось открыть выходной файл.");
                goto cleanup;
            }
        }

        int re = ffmpeg.avformat_write_header(oc, null);
        if (re < 0)
        {
            goto cleanup;
        }

       
        using (var fs = File.OpenRead(inputPath))
        using (var br = new BinaryReader(fs))
        {
            while (br.BaseStream.Position < br.BaseStream.Length)
            {
                // Читаем длину фрейма (2 байта)
                byte[] lengthBytes = br.ReadBytes(2);
                if (lengthBytes.Length < 2) break;

                int frameLen = lengthBytes[0] | (lengthBytes[1] << 8);
                if (frameLen <= 0 || frameLen > 65535) break;

                byte[] frameData = br.ReadBytes(frameLen);
                if (frameData.Length < frameLen) break;

                AVPacket pkt;
                ffmpeg.av_init_packet(&pkt);
                pkt.data = (byte*)ffmpeg.av_malloc((ulong)frameLen);
                Marshal.Copy(frameData, 0, (IntPtr)pkt.data, frameLen);
                pkt.size = frameLen;

                pkt.stream_index = st->index;

          
                int writeRet = ffmpeg.av_interleaved_write_frame(oc, &pkt);
                ffmpeg.av_free(pkt.data);

                if (writeRet < 0)
                {
                    Console.WriteLine("Ошибка при записи фрейма.");
                    break;
                }
            }
        }

     
        ffmpeg.av_write_trailer(oc);

    cleanup:
        if (oc != null && oc->pb != null)
        {
            ffmpeg.avio_closep(&oc->pb);
        }
        if (oc != null)
        {
            ffmpeg.avformat_free_context(oc);
        }
    }
}

Ответ или решение

Этапы инкапсуляции сырых фреймов Opus в OGG с использованием FFmpeg

В приведённом коде реализована функция для инкапсуляции сырых фреймов Opus в формате OGG с использованием библиотеки FFmpeg. Однако, возникает ошибка при выполнении функции ffmpeg.avformat_write_header, сообщающая о проблемах с записью заголовков Opus. Рассмотрим более подробно возможные причины этой проблемы и шаги по её исправлению.

1. Создание выходного формата

Для начала необходимо убедиться, что правильно создаётся и инициализируется выходной формат. В коде вы используете ffmpeg.av_guess_format, чтобы получить формат Ogg. Проверьте, что эта функция возвращает не нулевое значение:

AVOutputFormat* fmt = ffmpeg.av_guess_format("ogg", null, null);
if (fmt == null)
{
    Console.WriteLine("Не удалось определить выходной формат (ogg).");
    return;
}

Если формат не определяется, необходимо дополнительно проверить вашу установку FFmpeg и доступные форматы.

2. Создание форматного контекста

Вы создаёте новый форматный контекст с помощью ffmpeg.avformat_alloc_output_context2. Убедитесь, что контекст успешно создан:

AVFormatContext* oc = null;
ffmpeg.avformat_alloc_output_context2(&oc, fmt, null, null);
if (oc == null)
{
    Console.WriteLine("Не удалось создать выходной форматный контекст.");
    return;
}

3. Настройка аудиопотока

Ваша реализация для создания нового потока и настройки его параметров выглядит правильно. Однако важно убедиться, что параметры кодека правильно настроены, особенно extradata, где инициализируются заголовки Opus.

byte[] opusHeader = new byte[] {
    (byte)'O', (byte)'p', (byte)'u', (byte)'s', // Заголовок "Opus"
    1, // Версия
    2, // Количество каналов
    0, 0, // Предварительный пропуск
    0x80, 0xBB, 0, 0, // Оригинальная частота (48000 Гц)
    0, // Уровень громкости
    0 // Отображение каналов
};

Убедитесь, что все значения заголовка корректны, особенно original_sample_rate, которая должна соответствовать заданной частоте дискретизации.

4. Открытие файла для записи

Проверка успешного открытия выходного файла также важна:

int ret = ffmpeg.avio_open(&oc->pb, outputPath, ffmpeg.AVIO_FLAG_WRITE);
if (ret < 0)
{
    Console.WriteLine("Не удалось открыть выходной файл.");
    goto cleanup;
}

При возникновении ошибки необходимо диагностировать её, используя ffmpeg.av_strerror.

5. Запись заголовка

Ошибка на этапе вызова ffmpeg.avformat_write_header(oc, null) может быть связана с неправильной настройкой потоков или их параметров. Убедитесь, что все параметры, включая количество каналов и частоту дискретизации, соответствуют требованиям Opus.

int re = ffmpeg.avformat_write_header(oc, null);
if (re < 0)
{
    Console.WriteLine($"Ошибка записи заголовка: {re}");
    goto cleanup;
}

В случае возникновения ошибки, расшифруйте код ошибки для получения более подробной информации о причине.

6. Проверка записи фреймов

Во время записи фреймов необходимо следить за тем, чтобы структура данных AVPacket правильно инициализировалась:

AVPacket pkt;
ffmpeg.av_init_packet(&pkt);
pkt.data = (byte*)ffmpeg.av_malloc((ulong)frameLen);
Marshal.Copy(frameData, 0, (IntPtr)pkt.data, frameLen);
pkt.size = frameLen;

Убедитесь, что frameData корректно считывается и соответствует ожидаемой длине фрейма.

Заключение

Следуя приведённым рекомендациям и учитывая детали реализации, можно повысить шансы на успешную инкапсуляцию фреймов Opus в формат OGG. В случае возникновения ошибок обращайтесь к документации FFmpeg или обсуждайте проблемы на специализированных форумах, чтобы получить помощь от сообщества.

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

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