Вопрос или проблема
Мне нужно инкапсулировать необработанные фреймы 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 или обсуждайте проблемы на специализированных форумах, чтобы получить помощь от сообщества.