Проблема для декодирования видео H264 над RTP с FFMPEG (libavcodec)
-
28-09-2019 - |
Вопрос
Я установил Profile_IDC, Level_IDC, ExtraData et extraData_size of avcodeContext с набором параметра уровня профиля-ID ET SPROP SPROP.
Я разделяю декодирование кодированного среза, SPS, PPS и NAL_IDR_SLICE Packet:
В этом:
uint8_t start_sequence [] = {0, 0, 1}; INT SIZE = RECV (id_de_la_socket, (char *) rtpreceive, 65535,0);
Кодированный ломтик:
char *z = new char[size-16+sizeof(start_sequence)];
memcpy(z,&start_sequence,sizeof(start_sequence));
memcpy(z+sizeof(start_sequence),rtpReceive+16,size-16);
ConsumedBytes = avcodec_decode_video(codecContext,pFrame,&GotPicture,(uint8_t*)z,size-16+sizeof(start_sequence));
delete z;
Результат: потребляемые> 0 и GUTPICTURE> 0 (часто)
SPS и PPS:
идентичный код. Результат: потребляемые> 0 и GotPicture = 0
Это нормально, я думаю
Когда я нахожу новую пару SPS / PPS, я обновляю ExtraData и ExtraDa_size с полезными нагрузками этого пакета и их размера.
Nal_idr_slice:
Тип блока NAL составляет 28 => рамки IDR фрагментированы для того, чтобы я попробовал два метода для декодирования
1) Я префикс первый фрагмент (без заголовка RTP) с последовательностью 0x000001 и отправьте его на avcodec_decode_video. Затем я отправляю остальные фрагменты к этой функции.
2) I префикс первый фрагмент (без заголовка RTP) с последовательности 0x000001 и объединяю остальные фрагменты к нему. Я отправляю этот буфер в декодер.
В обоих случаях у меня нет ошибок (потребляемая> 0), но я обнаруживаю никакой рамки (GUTPICTURE = 0) ...
В чем проблема ?
Решение
В RTP все H264 I-Frames (IDR) обычно фрагментированы. Когда вы получите RTP, вы сначала должны пропустить заголовок (обычный первый 12 байтов), а затем добраться до единицы NAL (байт первой полезной нагрузки). Если NAL составляет 28 (1C), это означает, что следующая полезная нагрузка представляет один фрагмент H264 IDR (I-Frame) и что вам нужно собрать все из них для реконструкции H264 IDR (I-Frame).
Фрагментация происходит из-за ограниченного MTU и гораздо большего IDR. Один фрагмент может выглядеть так:
Фрагмент, который имеет начало bit = 1:
First byte: [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS]
Second byte: [ START BIT | END BIT | RESERVED BIT | 5 NAL UNIT BITS]
Other bytes: [... IDR FRAGMENT DATA...]
Другие фрагменты:
First byte: [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS]
Other bytes: [... IDR FRAGMENT DATA...]
Чтобы реконструировать IDR, вы должны собрать эту информацию:
int fragment_type = Data[0] & 0x1F;
int nal_type = Data[1] & 0x1F;
int start_bit = Data[1] & 0x80;
int end_bit = Data[1] & 0x40;
Если fragment_type == 28
Тогда полезная нагрузка, следующая за собой один фрагмент IDR. Следующий чек start_bit
Установите, если это так, то этот фрагмент является первым в последовательности. Вы используете его для реконструкции Nal Byte IDR, взяв первые 3 бита от первой полезной нагрузки. (3 NAL UNIT BITS)
и объединить их с последними 5 битами от второй полезной нагрузки (5 NAL UNIT BITS)
так что вы получите такое байт [3 NAL UNIT BITS | 5 NAL UNIT BITS]
. Анкет Затем напишите это NAL Byte первым в четкий буфер со всеми другими байтами из этого фрагмента. Не забудьте пропустить первый байт в последовательности, поскольку она не является частью IDR, а идентифицирует только фрагмент.
Если start_bit
и end_bit
Являются 0, просто напишите полезную нагрузку (пропуская первую посуду полезной нагрузки, которая идентифицирует фрагмент) в буфер.
Если start_bit равно 0 и end_bit - это 1, это означает, что это последний фрагмент, и вы просто пишете его полезную нагрузку (пропуская первый байт, который идентифицирует фрагмент) в буфер, и теперь у вас есть ваш IDR Reconstried.
Если вам нужен код, просто спросите в комментарии, я опубликую его, но я думаю, что это довольно ясно, как сделать ... =)
Относительно декодирования
Сегодня мне пришло в голову, почему вы получаете ошибку при декодировании IDR (я предполагал, что вы реконструировали это хорошо). Как вы создаете свою запись конфигурации декодера AVC? Есть ли в этом LIB, который вы используете, это автоматизировано? Если нет, и вы не слышали об этом, продолжайте читать ...
AVCDCR указан, чтобы позволить декодерам быстро анализировать все данные, необходимые для декодирования видеопотока H264 (AVC). И данные следующие:
- Профинансировать
- Профиль
- LevelIDC.
- SPS (наборы параметров последовательности)
- PPS (наборы параметров изображения)
Все эти данные отправляются в сеанс RTSP в SDP под полями: profile-level-id
и sprop-parameter-sets
.
Декодирование профиля - ID уровня
Строка идентификатора уровня Prifile разделена на 3 подстроения, каждый 2 символа длиной:
[PROFILE IDC][PROFILE IOP][LEVEL IDC]
Каждая подстрока представляет один байт в базе16Действительно Итак, если профиль IDC - 28, это означает, что это актуально 40 в base10. Позже вы будете использовать значения Base10, чтобы построить запись конфигурации декодера AVC.
Декодирование сборов-параметра
SPROPS - это обычные 2 строки (могут быть больше), которые являются разделенными запятой, и Base64 закодированДействительно Вы можете расшифровать их обоих, но нет необходимости. Ваша работа здесь просто для того, чтобы преобразовать их из строки Base64 в массив байтов для последующего использования. Теперь у вас есть 2 байтовых массива, первый массив US SPS, второй - PPS.
Строительство AVCDCR.
Теперь у вас есть все, что вам нужно для создания AVCDCR, вы начинаете с создания нового чистого буфера, теперь пишете эти вещи в нем в порядке, объясненном здесь:
1 - байт, который имеет ценность 1 и представляет версию
2 - профиль IDC Byte
3 - Prifile IOP -байт
4 - Уровень IDC Byte
5 - Байт со значением 0xff (Google The AVC Decoder Record, чтобы увидеть, что это такое)
6 - байт со значением 0xe1
7 - Короткое количество со значением длины массива SPS
8 - байтовая массива SPS
9 - Байт с количеством массивов PPS (у вас могут быть больше из них в SPROP-параметре)
10 - Коротко с длиной следующего массива PPS
11 - массив PPS
Декодирование видеопотока
Теперь у вас есть байтовый массив, который говорит декодеру, как декодировать видеопоток H264. Я верю, что вам нужно это, если ваш lib не строит его от SDP ...
Другие советы
Я не знаю о остальной части вашей реализации, но кажется вероятным, что «фрагменты», которые вы получаете, являются NAL подразделениями. Поэтому каждый может понадобиться начальный код NALU (00 00 01
или 00 00 00 01
) Прилагается при реконструкции бит -стрижки перед отправкой его в FFMPEG.
Во всяком случае, вы можете найти RFC для пакетизации H264 RTP полезно:
http://www.rfc-editor.org/rfc/rfc3984.txt
Надеюсь это поможет!
У меня есть реализация этого @ https://net7mma.codeplex.com/ Для C#, но процесс одинаково везде.
Вот соответствующий код
/// <summary>
/// Implements Packetization and Depacketization of packets defined in <see href="https://tools.ietf.org/html/rfc6184">RFC6184</see>.
/// </summary>
public class RFC6184Frame : Rtp.RtpFrame
{
/// <summary>
/// Emulation Prevention
/// </summary>
static byte[] NalStart = { 0x00, 0x00, 0x01 };
public RFC6184Frame(byte payloadType) : base(payloadType) { }
public RFC6184Frame(Rtp.RtpFrame existing) : base(existing) { }
public RFC6184Frame(RFC6184Frame f) : this((Rtp.RtpFrame)f) { Buffer = f.Buffer; }
public System.IO.MemoryStream Buffer { get; set; }
/// <summary>
/// Creates any <see cref="Rtp.RtpPacket"/>'s required for the given nal
/// </summary>
/// <param name="nal">The nal</param>
/// <param name="mtu">The mtu</param>
public virtual void Packetize(byte[] nal, int mtu = 1500)
{
if (nal == null) return;
int nalLength = nal.Length;
int offset = 0;
if (nalLength >= mtu)
{
//Make a Fragment Indicator with start bit
byte[] FUI = new byte[] { (byte)(1 << 7), 0x00 };
bool marker = false;
while (offset < nalLength)
{
//Set the end bit if no more data remains
if (offset + mtu > nalLength)
{
FUI[0] |= (byte)(1 << 6);
marker = true;
}
else if (offset > 0) //For packets other than the start
{
//No Start, No End
FUI[0] = 0;
}
//Add the packet
Add(new Rtp.RtpPacket(2, false, false, marker, PayloadTypeByte, 0, SynchronizationSourceIdentifier, HighestSequenceNumber + 1, 0, FUI.Concat(nal.Skip(offset).Take(mtu)).ToArray()));
//Move the offset
offset += mtu;
}
} //Should check for first byte to be 1 - 23?
else Add(new Rtp.RtpPacket(2, false, false, true, PayloadTypeByte, 0, SynchronizationSourceIdentifier, HighestSequenceNumber + 1, 0, nal));
}
/// <summary>
/// Creates <see cref="Buffer"/> with a H.264 RBSP from the contained packets
/// </summary>
public virtual void Depacketize() { bool sps, pps, sei, slice, idr; Depacketize(out sps, out pps, out sei, out slice, out idr); }
/// <summary>
/// Parses all contained packets and writes any contained Nal Units in the RBSP to <see cref="Buffer"/>.
/// </summary>
/// <param name="containsSps">Indicates if a Sequence Parameter Set was found</param>
/// <param name="containsPps">Indicates if a Picture Parameter Set was found</param>
/// <param name="containsSei">Indicates if Supplementatal Encoder Information was found</param>
/// <param name="containsSlice">Indicates if a Slice was found</param>
/// <param name="isIdr">Indicates if a IDR Slice was found</param>
public virtual void Depacketize(out bool containsSps, out bool containsPps, out bool containsSei, out bool containsSlice, out bool isIdr)
{
containsSps = containsPps = containsSei = containsSlice = isIdr = false;
DisposeBuffer();
this.Buffer = new MemoryStream();
//Get all packets in the frame
foreach (Rtp.RtpPacket packet in m_Packets.Values.Distinct())
ProcessPacket(packet, out containsSps, out containsPps, out containsSei, out containsSlice, out isIdr);
//Order by DON?
this.Buffer.Position = 0;
}
/// <summary>
/// Depacketizes a single packet.
/// </summary>
/// <param name="packet"></param>
/// <param name="containsSps"></param>
/// <param name="containsPps"></param>
/// <param name="containsSei"></param>
/// <param name="containsSlice"></param>
/// <param name="isIdr"></param>
internal protected virtual void ProcessPacket(Rtp.RtpPacket packet, out bool containsSps, out bool containsPps, out bool containsSei, out bool containsSlice, out bool isIdr)
{
containsSps = containsPps = containsSei = containsSlice = isIdr = false;
//Starting at offset 0
int offset = 0;
//Obtain the data of the packet (without source list or padding)
byte[] packetData = packet.Coefficients.ToArray();
//Cache the length
int count = packetData.Length;
//Must have at least 2 bytes
if (count <= 2) return;
//Determine if the forbidden bit is set and the type of nal from the first byte
byte firstByte = packetData[offset];
//bool forbiddenZeroBit = ((firstByte & 0x80) >> 7) != 0;
byte nalUnitType = (byte)(firstByte & Common.Binary.FiveBitMaxValue);
//o The F bit MUST be cleared if all F bits of the aggregated NAL units are zero; otherwise, it MUST be set.
//if (forbiddenZeroBit && nalUnitType <= 23 && nalUnitType > 29) throw new InvalidOperationException("Forbidden Zero Bit is Set.");
//Determine what to do
switch (nalUnitType)
{
//Reserved - Ignore
case 0:
case 30:
case 31:
{
return;
}
case 24: //STAP - A
case 25: //STAP - B
case 26: //MTAP - 16
case 27: //MTAP - 24
{
//Move to Nal Data
++offset;
//Todo Determine if need to Order by DON first.
//EAT DON for ALL BUT STAP - A
if (nalUnitType != 24) offset += 2;
//Consume the rest of the data from the packet
while (offset < count)
{
//Determine the nal unit size which does not include the nal header
int tmp_nal_size = Common.Binary.Read16(packetData, offset, BitConverter.IsLittleEndian);
offset += 2;
//If the nal had data then write it
if (tmp_nal_size > 0)
{
//For DOND and TSOFFSET
switch (nalUnitType)
{
case 25:// MTAP - 16
{
//SKIP DOND and TSOFFSET
offset += 3;
goto default;
}
case 26:// MTAP - 24
{
//SKIP DOND and TSOFFSET
offset += 4;
goto default;
}
default:
{
//Read the nal header but don't move the offset
byte nalHeader = (byte)(packetData[offset] & Common.Binary.FiveBitMaxValue);
if (nalHeader > 5)
{
if (nalHeader == 6)
{
Buffer.WriteByte(0);
containsSei = true;
}
else if (nalHeader == 7)
{
Buffer.WriteByte(0);
containsPps = true;
}
else if (nalHeader == 8)
{
Buffer.WriteByte(0);
containsSps = true;
}
}
if (nalHeader == 1) containsSlice = true;
if (nalHeader == 5) isIdr = true;
//Done reading
break;
}
}
//Write the start code
Buffer.Write(NalStart, 0, 3);
//Write the nal header and data
Buffer.Write(packetData, offset, tmp_nal_size);
//Move the offset past the nal
offset += tmp_nal_size;
}
}
return;
}
case 28: //FU - A
case 29: //FU - B
{
/*
Informative note: When an FU-A occurs in interleaved mode, it
always follows an FU-B, which sets its DON.
* Informative note: If a transmitter wants to encapsulate a single
NAL unit per packet and transmit packets out of their decoding
order, STAP-B packet type can be used.
*/
//Need 2 bytes
if (count > 2)
{
//Read the Header
byte FUHeader = packetData[++offset];
bool Start = ((FUHeader & 0x80) >> 7) > 0;
//bool End = ((FUHeader & 0x40) >> 6) > 0;
//bool Receiver = (FUHeader & 0x20) != 0;
//if (Receiver) throw new InvalidOperationException("Receiver Bit Set");
//Move to data
++offset;
//Todo Determine if need to Order by DON first.
//DON Present in FU - B
if (nalUnitType == 29) offset += 2;
//Determine the fragment size
int fragment_size = count - offset;
//If the size was valid
if (fragment_size > 0)
{
//If the start bit was set
if (Start)
{
//Reconstruct the nal header
//Use the first 3 bits of the first byte and last 5 bites of the FU Header
byte nalHeader = (byte)((firstByte & 0xE0) | (FUHeader & Common.Binary.FiveBitMaxValue));
//Could have been SPS / PPS / SEI
if (nalHeader > 5)
{
if (nalHeader == 6)
{
Buffer.WriteByte(0);
containsSei = true;
}
else if (nalHeader == 7)
{
Buffer.WriteByte(0);
containsPps = true;
}
else if (nalHeader == 8)
{
Buffer.WriteByte(0);
containsSps = true;
}
}
if (nalHeader == 1) containsSlice = true;
if (nalHeader == 5) isIdr = true;
//Write the start code
Buffer.Write(NalStart, 0, 3);
//Write the re-construced header
Buffer.WriteByte(nalHeader);
}
//Write the data of the fragment.
Buffer.Write(packetData, offset, fragment_size);
}
}
return;
}
default:
{
// 6 SEI, 7 and 8 are SPS and PPS
if (nalUnitType > 5)
{
if (nalUnitType == 6)
{
Buffer.WriteByte(0);
containsSei = true;
}
else if (nalUnitType == 7)
{
Buffer.WriteByte(0);
containsPps = true;
}
else if (nalUnitType == 8)
{
Buffer.WriteByte(0);
containsSps = true;
}
}
if (nalUnitType == 1) containsSlice = true;
if (nalUnitType == 5) isIdr = true;
//Write the start code
Buffer.Write(NalStart, 0, 3);
//Write the nal heaer and data data
Buffer.Write(packetData, offset, count - offset);
return;
}
}
}
internal void DisposeBuffer()
{
if (Buffer != null)
{
Buffer.Dispose();
Buffer = null;
}
}
public override void Dispose()
{
if (Disposed) return;
base.Dispose();
DisposeBuffer();
}
//To go to an Image...
//Look for a SliceHeader in the Buffer
//Decode Macroblocks in Slice
//Convert Yuv to Rgb
}
Существуют также реализации для различных других предложений, которые помогают заставить медиаатрагионы в медиаэлементе или в другом программном обеспечении или просто сохранять его на диск.
Продолжается запись в формат контейнера.