Я разрабатываю приложение, которое должно публиковать медиапоток на URL-адрес «проглатывания» rtmp (используемый в YouTube Live или в качестве входа в Wowza Streaming Engine и т. Д.).), и я использую библиотеку ffmpeg (программно, из C/C++, а не инструмент командной строки) для обработки уровня rtmp. У меня рабочая версия готова, но я вижу некоторые проблемы при потоковой передаче потоков с большей пропускной способностью на серверы с худшим пингом. Проблема существует как при использовании встроенной rtmp-реализации ffmpeg, так и реализации librtmp.Поток публикации с использованием ffmpeg rtmp: пропускная способность сети не полностью используется
При переходе на локальный целевой сервер с низким уровнем пинга через хорошую сеть (в частности, локальный сервер Wowza) мой код до сих пор обрабатывал каждый поток, который я выбрал для него, и смог загрузить все в режиме реального времени - что важно, поскольку это подразумевается исключительно для живых потоков.
Однако при потоковой передаче на удаленный сервер с худшим пингом (например, URL-адрес запроса на YouTube на a.rtmp.youtube.com, который для меня имеет 50 + ms пинг), потоки с низкой пропускной способностью работают нормально, но с более высоким потоки полосы пропускания, сеть недоиспользуется - например, для потока 400 кБ/с, я вижу только использование сети ~ 140 кБ/с, при этом много кадров получает задержку/отброс, в зависимости от стратегии, которую я использую для работы с сетью отталкивать.
Теперь я знаю, что это не проблема с сетевым подключением к целевому серверу, потому что я могу успешно загрузить поток в реальном времени при использовании инструмента командной строки ffmpeg на один и тот же целевой сервер или используя мой код для потока к локальному серверу Wowza, который затем перенаправляет поток в точку приема в YouTube.
Таким образом, сетевое соединение не является проблемой, и проблема, похоже, связана с моим кодом.
Я приурочена различные части моего кода и обнаружил, что, когда появляется проблема, вызовов av_write_frame/av_interleaved_write_frame (я никогда не смешивайте & соответствовать им, я всегда использовать одну версию последовательно в какой-либо конкретной сборке, это просто, что я я экспериментировал с обоими, чтобы увидеть, есть ли какая-то разница), иногда занимает очень много времени - я видел, что эти звонки иногда занимают до 500-1000 мс, хотя средний «плохой случай» находится в диапазоне 50-100 мс. Не все призывы к ним берут это долгое время, большинство возвращаются мгновенно, но среднее время, затрачиваемое на эти вызовы, растет больше, чем средняя продолжительность кадра, поэтому я больше не получаю загрузку в режиме реального времени.
Основным подозреваемым, как мне кажется, может быть механизм окна подтверждения подтверждения rtmp, когда отправитель данных ожидает подтверждения получения после отправки каждого N байта, прежде чем отправлять какие-либо данные - это объясняет доступную сеть пропускная способность не полностью используется, поскольку клиент просто сидел бы там и ждал ответа (который занимает больше времени из-за более низкого пинга) вместо использования доступной полосы пропускания. Хотя я не смотрел код ftmpeg rtmp/librtmp, чтобы увидеть, действительно ли он реализует такой дросселирование, так что это может быть что-то другое.
Полный код приложения слишком много, чтобы разместить здесь, но вот некоторые важные фрагменты:
Формат создания контекста: создание
const int nAVFormatContextCreateError = avformat_alloc_output_context2(&m_pAVFormatContext, nullptr, "flv", m_sOutputUrl.c_str());
Поток:
m_pVideoAVStream = avformat_new_stream(m_pAVFormatContext, nullptr);
m_pVideoAVStream->id = m_pAVFormatContext->nb_streams - 1;
m_pAudioAVStream = avformat_new_stream(m_pAVFormatContext, nullptr);
m_pAudioAVStream->id = m_pAVFormatContext->nb_streams - 1;
Видео поток:
m_pVideoAVStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
m_pVideoAVStream->codecpar->codec_id = AV_CODEC_ID_H264;
m_pVideoAVStream->codecpar->width = nWidth;
m_pVideoAVStream->codecpar->height = nHeight;
m_pVideoAVStream->codecpar->format = AV_PIX_FMT_YUV420P;
m_pVideoAVStream->codecpar->bit_rate = 10 * 1000 * 1000;
m_pVideoAVStream->time_base = AVRational { 1, 1000 };
m_pVideoAVStream->codecpar->extradata_size = int(nTotalSizeRequired);
m_pVideoAVStream->codecpar->extradata = (uint8_t*)av_malloc(m_pVideoAVStream->codecpar->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
// Fill in the extradata here - I'm sure I'm doing that correctly.
Аудио настройки потока:
m_pAudioAVStream->time_base = AVRational { 1, 1000 };
// Let's leave creation of m_pAudioCodecContext out of the scope of this question, I'm quite sure everything is done right there.
const int nAudioCodecCopyParamsError = avcodec_parameters_from_context(m_pAudioAVStream->codecpar, m_pAudioCodecContext);
Открытие соединения:
const int nAVioOpenError = avio_open2(&m_pAVFormatContext->pb, m_sOutputUrl.c_str(), AVIO_FLAG_WRITE);
Запуск потока:
AVDictionary * pOptions = nullptr;
const int nWriteHeaderError = avformat_write_header(m_pAVFormatContext, &pOptions);
Отправка видеокадра:
AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.dts = nTimestamp;
pkt.pts = nTimestamp;
pkt.duration = nDuration; // I know what I have the wrong duration sometimes, but I don't think that's the issue.
pkt.data = pFrameData;
pkt.size = pFrameDataSize;
pkt.flags = bKeyframe ? AV_PKT_FLAG_KEY : 0;
pkt.stream_index = m_pVideoAVStream->index;
const int nWriteFrameError = av_write_frame(m_pAVFormatContext, &pkt); // This is where too much time is spent.
Sen ding a audio frame:
AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.pts = m_nTimestampMs;
pkt.dts = m_nTimestampMs;
pkt.duration = m_nDurationMs;
pkt.stream_index = m_pAudioAVStream->index;
const int nWriteFrameError = av_write_frame(m_pAVFormatContext, &pkt);
Любые идеи? Я на правильном пути, думая о Окне подтверждения? Я что-то другое делаю неправильно?