Декодирование RTP пакета и извлечение аудио данных для сохранения в mka формат
Я хочу записывать аудио данные, полученные через RTP пакеты в аудиофайл, формата mka. Для записи этих аудио данных в файл, я буду использовать библиотеку ffmpeg. Но прежде чем записывать эти аудио данные, их нужно извлечь. И тут у меня проблема! Я не знаю, как правильно извлечь эти самые аудио данные. Известно, что по RTP передаются различные форматы аудио и видео данных, и каждый этот формат имеет свой заголовок. Как мне определить, какой формат я получил, и как мне извлечь эти данные из определенного формата ?
Для начала, я создал класс, в котором буду сохранять лишь заголовок пакета:
class RtpHeader
{
public:
uint16_t m_v :2; // Protocol version : 2bits
uint16_t m_p :1; // Padding flag : 1bits
uint16_t m_x :1; // Header extension flag : 1bits
uint16_t m_cc:4; // CSRC count : 4bits
uint16_t m_m :1; // Marker bit : 1bits
uint16_t m_pt:7; // Payload type : 7bits
uint16_t m_nSequence; // Sequence number : 16bits
uint32_t m_timestamp; // Timestamp : 32bits
uint32_t m_ssrc; // Synchronization source : 32bits
};
А также, и сам класс, в котором я сохраняю полностью пакет RTP, с самим заголовок и с самими медиа данными:
class RtpPacket
{
public:
RtpHeader m_header;
u_int8_t* p_payload;
u_int32_t m_payloadLen;
u_int8_t* p_packet;
u_int32_t m_packetLen;
};
Как все это пополнить ПРАВИЛЬНО, я не знаю. Знаю лишь, что на входе я получаю буфер uint8_t, в котором и хранится сам RTP пакет.
UPDATE
Я написал пример того, как я это себе представляю. Верен ли этот пример, и если нет, то почему?
int8_t RtpDecoder::decompose(RtpPacket *p_packet) noexcept
{
if ((p_packet->m_packetLen < MAX_RTP_PACKET_SIZE) &&
(p_packet->m_packetLen > MIN_RTP_PACKET_SIZE)) {
if ((p_packet->p_payload != nullptr) &&
(p_packet->p_packet != nullptr)) {
// Extract header information
p_packet->m_header.m_v = ((p_packet->p_packet[0] >> 6) & 0x03);
p_packet->p_packet = reinterpret_cast<u_int8_t*>(((p_packet->p_packet[0] >> 5) & 0x01));
p_packet->m_header.m_x = (p_packet->p_packet[0] >> 4) & 0x01;
p_packet->m_header.m_cc = (p_packet->p_packet[0] >> 0) & 0x0F;
p_packet->m_header.m_m = (p_packet->p_packet[1] >> 7) & 0x01;
p_packet->m_header.m_pt = (p_packet->p_packet[1] >> 0) & 0x7F;
memcpy (&p_packet->m_header.m_nSequence, &p_packet->p_packet[2], 2);
p_packet->m_header.m_nSequence = ntohs((uint16_t)p_packet->m_header.m_nSequence);
memcpy (&p_packet->m_header.m_timestamp, &p_packet->p_packet[4], 4);// change to shifts for unified byte sex
p_packet->m_header.m_timestamp = ntohl(p_packet->m_header.m_timestamp);
memcpy (&p_packet->m_header.m_ssrc, &p_packet->p_packet[8], 4);// change to shifts for unified byte sex
p_packet->m_header.m_ssrc = ntohl(p_packet->m_header.m_ssrc);
// header consistency checks
if ((p_packet->m_header.m_v != 2)
|| (p_packet->p_packet != nullptr)
|| (p_packet->m_header.m_x != 0)
|| (p_packet->m_header.m_cc != 0) )
{
std::cerr << "RTP header consistency problem, header follows" << std::endl;
printPacket(p_packet);
return -1;
}
p_packet->m_payloadLen = p_packet->m_packetLen-12;
memcpy (reinterpret_cast<void*>(p_packet->m_payloadLen),
reinterpret_cast<void*>(&p_packet->p_packet[12]),
static_cast<std::size_t>(p_packet->m_payloadLen));
return 0;
}
}
return -1;
}
void RtpDecoder::printPacket(RtpPacket *packet) noexcept
{
if (packet != nullptr) {
int i;
for (i=0; i< 30; i++) {
printf ("%02x ", packet->p_packet[i]);
}
printf ("Version (V): %d\n", (int) packet->m_header.m_v);
printf ("Padding (P): %d\n", (int) *packet->p_packet);
printf ("Extension (X): %d\n", (int) packet->m_header.m_x);
printf ("CSRC count (CC): %d\n", (int) packet->m_header.m_cc);
printf ("Marker bit (M): %d\n", (int) packet->m_header.m_m);
printf ("Payload Type (PT): %d\n", (int) packet->m_header.m_pt);
printf ("Sequence Number: %d\n", (int) packet->m_header.m_nSequence);
printf ("Timestamp: %d\n", (int) packet->m_header.m_timestamp);
printf ("SSRC: %d\n", (int) packet->m_header.m_ssrc);
}
}
Ответы (1 шт):
PS. Написано на Qt
Прочитал некоторые гайды и документацию по RTP и написал рабочую версию декодирования RTP пакета и извлечения аудио данных:
Во-превых, я переделал концепцию сохранения данных заголовка пакета и полезную нагрузку:
class RtpHeader
{
public:
uint16_t m_v :2; // Protocol version : 2bits
uint16_t m_p :1; // Padding flag : 1bits
uint16_t m_x :1; // Header extension flag : 1bits
uint16_t m_cc:4; // CSRC count : 4bits
uint16_t m_m :1; // Marker bit : 1bits
uint16_t m_pt:7; // Payload type : 7bits
uint16_t m_nSequence; // Sequence number : 16bits
uint32_t m_timestamp; // Timestamp : 32bits
uint32_t m_ssrc; // Synchronization source : 32bits
}; // RtpHeader
static constexpr uint16_t MAX_RTP_PAYLOAD_LEN = 65536 - 40;
static constexpr uint16_t MIN_RTP_PACKET_SIZE = 12;
static constexpr uint16_t MAX_RTP_PACKET_SIZE = 65536 - 28;
class RtpPacket
{
public:
RtpHeader m_hdr;
u_int32_t m_pldLen;
u_int8_t* p_pld;
u_int32_t m_pckLen;
}; // RtpPacket
Так же, переделал класс парсера Rtp пакета:
class RtpParser
{
public:
enum Value : uint8_t
{
VERSION = 0,
PADDING = 1,
HDRFLAG = 2,
CSRC = 3,
MARKBIT = 4,
PAYLOAD = 5,
SEQNUMB = 6,
TIMESTAMP = 7,
SSRC = 8,
};
public:
RtpParser(void) noexcept = delete;
RtpParser &operator=(const RtpParser &) noexcept = delete;
RtpParser &operator=(RtpParser &&) noexcept = delete;
RtpParser(const RtpParser &) noexcept = delete;
RtpParser(RtpParser &&) noexcept = delete;
~RtpParser(void) noexcept = delete;
public:
static int8_t decompose(const QByteArray &array) noexcept;
static uint32_t getRtpValue(const uint8_t *packet, const Value &value) noexcept;
static void printPacket(const RtpPacket *packet) noexcept;
}; // RtpParser
uint32_t
RtpParser::getRtpValue(
const uint8_t *packet, const Value &value) noexcept
{
uint32_t accum = 0;
if (packet != nullptr) {
uint32_t offset = 0;
uint32_t bCount = 0;
switch (value) {
case RtpParser::Value::SSRC :
offset = 64; bCount = 32; break;
case RtpParser::Value::CSRC :
offset = 4; bCount = 4; break;
case RtpParser::Value::VERSION :
offset = 0; bCount = 2; break;
case RtpParser::Value::PADDING :
offset = 2; bCount = 1; break;
case RtpParser::Value::HDRFLAG :
offset = 3; bCount = 1; break;
case RtpParser::Value::MARKBIT :
offset = 8; bCount = 1; break;
case RtpParser::Value::PAYLOAD :
offset = 9; bCount = 7; break;
case RtpParser::Value::SEQNUMB :
offset = 16; bCount = 16; break;
case RtpParser::Value::TIMESTAMP :
offset = 32; bCount = 32; break;
default :
offset = 0; bCount = 0; break;
}
while (bCount != 0) {
const u_int8_t *curSrc = packet + offset / 8;
const uint32_t bitsLeft = 8 - offset % 8;
const u_int8_t curData = *curSrc << (8 - bitsLeft);
const uint32_t bitsToUse = bitsLeft < bCount ? bitsLeft : bCount;
accum <<= bitsToUse;
uint8_t mask = (1 << bitsToUse) - 1;
const uint32_t off = 8 - bitsToUse;
accum |= (curData & (mask << off)) >> off;
offset += bitsToUse;
bCount -= bitsToUse;
}
}
return accum;
}
int8_t RtpParser::decompose(const QByteArray &array) noexcept
{
if (array.size() <= MAX_RTP_PACKET_SIZE &&
array.size() >= MIN_RTP_PACKET_SIZE) {
RtpPacket *p_packet = new RtpPacket;
if (p_packet == nullptr) {
return -1;
}
p_packet->m_pckLen = array.size();
const uint8_t *packet =
reinterpret_cast<const uint8_t*>(array.data());
if ((p_packet->m_hdr.m_pt = RtpParser::getRtpValue(
packet, RtpParser::Value::PAYLOAD)) >= 0x13) {
qDebug() << "An unsupported rtp package was received: "
"the payload is not supported";
return -1;
}
if ((p_packet->m_hdr.m_v = RtpParser::getRtpValue(
packet, RtpParser::Value::VERSION)) != 0x2 ) {
qDebug() << "An unsupported rtp package was received: "
"the version is not supported";
return -1;
}
if ((p_packet->m_hdr.m_p = RtpParser::getRtpValue(
packet, RtpParser::Value::PADDING)) != 0x0 ) {
qDebug() << "An unsupported rtp package was received: "
"the padding is not supported";
return -1;
}
if ((p_packet->m_hdr.m_x = RtpParser::getRtpValue(
packet, RtpParser::Value::HDRFLAG)) != 0x0 ) {
qDebug() << "An unsupported rtp package was received: "
"the extens. is not supported";
return -1;
}
if ((p_packet->m_hdr.m_cc = RtpParser::getRtpValue(
packet, RtpParser::Value::CSRC )) != 0x0 ) {
qDebug() << "An unsupported rtp package was received: "
"the csrc-c. is not supported";
return -1;
}
p_packet->m_hdr.m_m =
RtpParser::getRtpValue(packet, RtpParser::Value::MARKBIT );
p_packet->m_hdr.m_nSequence =
RtpParser::getRtpValue(packet, RtpParser::Value::SEQNUMB );
p_packet->m_hdr.m_timestamp =
RtpParser::getRtpValue(packet, RtpParser::Value::TIMESTAMP);
p_packet->m_hdr.m_ssrc =
RtpParser::getRtpValue(packet, RtpParser::Value::SSRC );
p_packet->m_pldLen = p_packet->m_pckLen - MIN_RTP_PACKET_SIZE;
if (p_packet->m_pldLen <= MAX_RTP_PAYLOAD_LEN && p_packet->m_pldLen >= MIN_RTP_PACKET_SIZE) {
p_packet->p_pld = new uint8_t[MAX_RTP_PAYLOAD_LEN];
if (p_packet->p_pld != nullptr) {
memcpy(p_packet->p_pld, &packet[MIN_RTP_PACKET_SIZE], p_packet->m_pldLen);
// For debugging: print all info about rtp packet
RtpParser::printPacket(p_packet);
//todo Further processing of the payload itself...
// ...
return 0;
}
}
}
return -1;
}
void RtpParser::printPacket(const RtpPacket *packet) noexcept
{
auto getSsrcAsStr = [&packet]() -> QString {
return QString::number(packet->m_hdr.m_ssrc);
};
auto getCsrcAsStr = [&packet]() -> QString {
return QString::number(packet->m_hdr.m_cc);
};
auto getVersionAsStr = [&packet]() -> QString {
return QString::number(packet->m_hdr.m_v);
};
auto getSeqNumbAsStr = [&packet]() -> QString {
return QString::number(packet->m_hdr.m_nSequence);
};
auto getPaddingAsStr = [&packet]() -> QString {
return packet->m_hdr.m_p == 0 ? "Fasle" : "True";
};
auto getMarkBitAsStr = [&packet]() -> QString {
return packet->m_hdr.m_m == 0 ? "Fasle" : "True";
};
auto getHdrFlagAsStr = [&packet]() -> QString {
return packet->m_hdr.m_x == 0 ? "Fasle" : "True";
};
auto getPayloadAsStr = [&packet]() -> QString {
switch (packet->m_hdr.m_pt) {
case 0: return "Name[PCMU ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 1: return "Name[1016 ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 2: return "Name[G721 ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 3: return "Name[GSM ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 4: return "Name[G723 ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[********]";
case 5: return "Name[DVI4 ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 6: return "Name[DVI4 ], Type[Audio], Clock rate (Hz)[16000], "
"Audio channels[1], references[RFC 3551]";
case 7: return "Name[LPC ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 8: return "Name[PCMA ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 9: return "Name[G722 ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 10: return "Name[L16 ], Type[Audio], Clock rate (Hz)[44100], "
"Audio channels[2], references[RFC 3551]";
case 11: return "Name[L16 ], Type[Audio], Clock rate (Hz)[44100], "
"Audio channels[1], references[RFC 3551]";
case 12: return "Name[QCELP], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[********]";
case 13: return "Name[CN ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3389]";
case 14: return "Name[MPA ], Type[Audio], Clock rate (Hz)[90000], "
"Audio channels[ ], references[RFC 2250]";
case 15: return "Name[G728 ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[RFC 3551]";
case 16: return "Name[DVI4 ], Type[Audio], Clock rate (Hz)[11025], "
"Audio channels[1], references[********]";
case 17: return "Name[DVI4 ], Type[Audio], Clock rate (Hz)[22050], "
"Audio channels[1], references[********]";
case 18: return "Name[G729 ], Type[Audio], Clock rate (Hz)[8000 ], "
"Audio channels[1], references[********]";
default: return "Unsupported";
}
};
QString payloadAsStr;
for (uint8_t i = 0; i < packet->m_pldLen; i++) {
payloadAsStr.push_back(QString::number(packet->p_pld[i], 16));
}
qDebug() << "Version : " << getVersionAsStr();
qDebug() << "Padding : " << getPaddingAsStr();
qDebug() << "HdrFlag : " << getHdrFlagAsStr();
qDebug() << "MarkBit : " << getMarkBitAsStr();
qDebug() << "Payload : " << getPayloadAsStr();
qDebug() << "SeqNumb : " << getSeqNumbAsStr();
qDebug() << "Ssrc : " << getSsrcAsStr();
qDebug() << "Csrc : " << getCsrcAsStr();
qDebug() << "Timestamp : " << QString::number(packet->m_hdr.m_timestamp);
qDebug() << "Payload : " << payloadAsStr;
}
Все это гарантирует вывод содержимого RTP пакета, и выделения полезной нагрузки от заголовка. Для примера работоспособности программы, я выведу информацию RTP заголовка, используя первый RTP пакет содержимого rtp_example.raw.gz. Данный файл можно и нужно открыть программой Wireshark.
PS: Для прочтения RTP пакета, я сохранил отдельно сам RTP пакет в сыром виде, так как, в файле rtp_example.raw.gz, пакеты RTP не постовляются в чистом, отдельном виде, и что бы их получить без предварительного сохранения, придется парсить и IP, UDP пакеты.
#include <QFile>
#include "rtpparser.h"
#include <QCoreApplication>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
FILE *f = fopen("/path_to_raw_rtp_packet/raw_rtp_packet", "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
auto *string = (uint8_t *) malloc(fsize + 1);
fread(string, fsize, 1, f);
fclose(f);
QByteArray array;
for (int i = 0; i < fsize; i++) {
array.push_back(string[i]);
}
if (RtpParser::decompose(array) == -1) {
qDebug() << "Decompose error";
}
return a.exec();
}
// Console output
// Version : "2"
// Padding : "Fasle"
// HdrFlag : "Fasle"
// MarkBit : "True"
// Payload : "Name[PCMA], Type[Audio], Clock rate (Hz)[8000], Audio channels[1], references[RFC 3551]"
// SeqNumb : "59133"
// Ssrc : "3739283087"
// Csrc : "0"
// Timestamp : "240"
// Payload :
// "d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5
// d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5"
Проверяем введенную информацию с нашей программой Wireshark:
Все работает :)
И тут появляется другая проблема. По типу полезной нагрузки, мы определяем какой формат медиа записан. Благодаря этому, мы определяем какой парсер использовать, так как форматы различаются. Для всего этого, нам потребуется написать всевозможные парсеры для поддерживаемых медиа форматов. А это уже другая история...
PS: Если кому либо, потребуется код на с++, без использования QT, напишите комментарий, я перепишу и опубликую. А если есть предложения по оптимизации этого кода, либо какието другие предложения и вопросы, я буду благодарен и рад ответить на них.
