LIVE555 如何使用 h264 framer class 获取 ffmpeg 的最终单位
LIVE555 how to use h264 framer class to get nal units for ffmpeg
我正在尝试创建一个小型应用程序,它将保存来自正在使用的 h264 流的帧。
我以一个 testRTSP 程序为例,在 ffmpeg 库的帮助下对 DummySink::afterGettingFrame
函数进行了一些更改以解码帧。
正如我从 frameSize 了解到的,我的前两帧是 SPS 单元,所以我将它们与我的第三帧连接起来,然后将新的大帧发送到 ffmpeg 解码器。但这不起作用。 ffmpeg 告诉我我的第一帧对于 SPS 来说太大了,然后它告诉我没有帧......我不知道我需要在这里改变什么。
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned /*durationInMicroseconds*/)
{
u_int8_t start_code[4] = { 0x00, 0x00, 0x00, 0x01 };
int stCodeLen = 4;
if (frameSize == 50)
{
//add start code
memcpy(bufferWithStartCode, start_code, stCodeLen);
shiftPtr += stCodeLen;
memcpy(bufferWithStartCode + shiftPtr, fReceiveBuffer, frameSize);
shiftPtr += frameSize;
}
else if (frameSize == 4)
{
memcpy(bufferWithStartCode + shiftPtr, fReceiveBuffer, frameSize);
shiftPtr += frameSize;
}
else
{
if (shiftPtr == 0)
{
memcpy(bufferWithStartCode, start_code, stCodeLen);
shiftPtr += stCodeLen;
}
memcpy(bufferWithStartCode + shiftPtr, fReceiveBuffer, frameSize);
avpkt.size = frameSize + shiftPtr;
avpkt.data = bufferWithStartCode;
shiftPtr = 0;
if (!avcodec_send_packet(cContext, &avpkt))
{
envir() << "error sending to decoder";
}
if (!avcodec_receive_frame(cContext, picture))
{
envir() << "error rx from decoder";
}
if (picture)
{
FILE *f;
char buffer[32]; // The filename buffer.
snprintf(buffer, sizeof(char) * 32, "file%i.txt", frame_num);
f = fopen(buffer, "w");
fprintf(f, "P5\n%d %d\n%d\n", fSubsession.videoWidth(), fSubsession.videoHeight(), 255);
for (int i = 0;i < fSubsession.videoHeight();i++)
fwrite(picture->data[0] + i * (picture->linesize[0]), 1, fSubsession.videoWidth(), f);
fclose(f);
}
}
envir() << frameSize << "\n";
frame_num++;
// Then continue, to request the next frame of data:
continuePlaying();
您的代码未显示您如何初始化编解码器,但 SPS 和 PPS 不应包含在数据包中。相反,它们应该在初始化时通过 AVCodecContext 的 extradata
字段传递给编解码器。然后你只需将实际的帧NAL传递给解码器即可获得解码图片。
我建议您在收到第一个 SPS 或响应 DESCRIBE
的 SDP 数据事件时初始化解码器。
我找到了解决问题的方法。
在live555的"testRTSP"例子中有函数void DummySink::afterGettingFrame(...)
。
我需要做的就是在每次函数获得框架时组装我的框架:
[start_code sps pps start_code frame_data]
frame_data此时是fReceiveBuffer
。
start_code 是字符数组 [0,0,0,1].
并将新数据推送到 ffmpeg 解码器:
m_packet.size = frameBufSize + frameSize; // size of assembled frame
m_packet.data = frameBuf; // assembled frame
if (avcodec_send_packet(m_decoderContext, &m_packet) != 0)
{
envir() << "error in sending packet to decoder" << "\n";
}
if (avcodec_receive_frame(m_decoderContext, pFrame) == 0)
decoderContext没有额外的设置!只需像教程中那样初始化所有内容(http://dranger.com/ffmpeg/ - 这是 ffmpeg C++ 库的最新教程),你就可以开始了。
我使用 memcpy
将数据合并到一个大数组中。
memcpy(frameBuf, startCode, 4);
frameBufSize += 4;
for (int i = 0; i < numSPropRecords; i++)
{
memcpy(frameBuf + frameBufSize, sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength);
frameBufSize += sPropRecords[i].sPropLength;
}
memcpy(frameBuf + frameBufSize, startCode, 4);
frameBufSize += 4;
memcpy(frameBuf + frameBufSize, fReceiveBuffer, frameSize);
m_packet.size = frameBufSize + frameSize;
m_packet.data = frameBuf;
您可以从子会话中获取 sps 和 pps 数据(查看 "openRTSP" 了解详细示例或 "H264orH264FileSink.h")
spsppsunits = subsession.fmtp_spropparametersets();
sPropRecords = parseSPropParameterSets(spsppsunits, numSPropRecords);
更新 1:
一段时间后,我发现我使用 ffmpeg 的方法存在错误。如果您不使用 sps 和 pps 单元提供额外数据信息,有时 ffmpeg 解码器将无法使用 H264 流。所以,
m_decoderContext->extradata = (uint8_t*)av_malloc(100 + AV_INPUT_BUFFER_PADDING_SIZE);
int extraDataSize = 0;
for (int i = 0; i < numSPropRecords; i++)
{
memcpy(m_decoderContext->extradata + extraDataSize, startCode, 4);
extraDataSize += 4;
memcpy(m_decoderContext->extradata + extraDataSize, sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength);
extraDataSize += sPropRecords[i].sPropLength;
}
m_decoderContext->extradata_size = extraDataSize;
之后就不需要每次都给frame提供sps和pps数据了,只需要起始码就可以了。
我正在尝试创建一个小型应用程序,它将保存来自正在使用的 h264 流的帧。
我以一个 testRTSP 程序为例,在 ffmpeg 库的帮助下对 DummySink::afterGettingFrame
函数进行了一些更改以解码帧。
正如我从 frameSize 了解到的,我的前两帧是 SPS 单元,所以我将它们与我的第三帧连接起来,然后将新的大帧发送到 ffmpeg 解码器。但这不起作用。 ffmpeg 告诉我我的第一帧对于 SPS 来说太大了,然后它告诉我没有帧......我不知道我需要在这里改变什么。
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned /*durationInMicroseconds*/)
{
u_int8_t start_code[4] = { 0x00, 0x00, 0x00, 0x01 };
int stCodeLen = 4;
if (frameSize == 50)
{
//add start code
memcpy(bufferWithStartCode, start_code, stCodeLen);
shiftPtr += stCodeLen;
memcpy(bufferWithStartCode + shiftPtr, fReceiveBuffer, frameSize);
shiftPtr += frameSize;
}
else if (frameSize == 4)
{
memcpy(bufferWithStartCode + shiftPtr, fReceiveBuffer, frameSize);
shiftPtr += frameSize;
}
else
{
if (shiftPtr == 0)
{
memcpy(bufferWithStartCode, start_code, stCodeLen);
shiftPtr += stCodeLen;
}
memcpy(bufferWithStartCode + shiftPtr, fReceiveBuffer, frameSize);
avpkt.size = frameSize + shiftPtr;
avpkt.data = bufferWithStartCode;
shiftPtr = 0;
if (!avcodec_send_packet(cContext, &avpkt))
{
envir() << "error sending to decoder";
}
if (!avcodec_receive_frame(cContext, picture))
{
envir() << "error rx from decoder";
}
if (picture)
{
FILE *f;
char buffer[32]; // The filename buffer.
snprintf(buffer, sizeof(char) * 32, "file%i.txt", frame_num);
f = fopen(buffer, "w");
fprintf(f, "P5\n%d %d\n%d\n", fSubsession.videoWidth(), fSubsession.videoHeight(), 255);
for (int i = 0;i < fSubsession.videoHeight();i++)
fwrite(picture->data[0] + i * (picture->linesize[0]), 1, fSubsession.videoWidth(), f);
fclose(f);
}
}
envir() << frameSize << "\n";
frame_num++;
// Then continue, to request the next frame of data:
continuePlaying();
您的代码未显示您如何初始化编解码器,但 SPS 和 PPS 不应包含在数据包中。相反,它们应该在初始化时通过 AVCodecContext 的 extradata
字段传递给编解码器。然后你只需将实际的帧NAL传递给解码器即可获得解码图片。
我建议您在收到第一个 SPS 或响应 DESCRIBE
的 SDP 数据事件时初始化解码器。
我找到了解决问题的方法。
在live555的"testRTSP"例子中有函数void DummySink::afterGettingFrame(...)
。
我需要做的就是在每次函数获得框架时组装我的框架:
[start_code sps pps start_code frame_data]
frame_data此时是fReceiveBuffer
。
start_code 是字符数组 [0,0,0,1].
并将新数据推送到 ffmpeg 解码器:
m_packet.size = frameBufSize + frameSize; // size of assembled frame
m_packet.data = frameBuf; // assembled frame
if (avcodec_send_packet(m_decoderContext, &m_packet) != 0)
{
envir() << "error in sending packet to decoder" << "\n";
}
if (avcodec_receive_frame(m_decoderContext, pFrame) == 0)
decoderContext没有额外的设置!只需像教程中那样初始化所有内容(http://dranger.com/ffmpeg/ - 这是 ffmpeg C++ 库的最新教程),你就可以开始了。
我使用 memcpy
将数据合并到一个大数组中。
memcpy(frameBuf, startCode, 4);
frameBufSize += 4;
for (int i = 0; i < numSPropRecords; i++)
{
memcpy(frameBuf + frameBufSize, sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength);
frameBufSize += sPropRecords[i].sPropLength;
}
memcpy(frameBuf + frameBufSize, startCode, 4);
frameBufSize += 4;
memcpy(frameBuf + frameBufSize, fReceiveBuffer, frameSize);
m_packet.size = frameBufSize + frameSize;
m_packet.data = frameBuf;
您可以从子会话中获取 sps 和 pps 数据(查看 "openRTSP" 了解详细示例或 "H264orH264FileSink.h")
spsppsunits = subsession.fmtp_spropparametersets();
sPropRecords = parseSPropParameterSets(spsppsunits, numSPropRecords);
更新 1:
一段时间后,我发现我使用 ffmpeg 的方法存在错误。如果您不使用 sps 和 pps 单元提供额外数据信息,有时 ffmpeg 解码器将无法使用 H264 流。所以,
m_decoderContext->extradata = (uint8_t*)av_malloc(100 + AV_INPUT_BUFFER_PADDING_SIZE);
int extraDataSize = 0;
for (int i = 0; i < numSPropRecords; i++)
{
memcpy(m_decoderContext->extradata + extraDataSize, startCode, 4);
extraDataSize += 4;
memcpy(m_decoderContext->extradata + extraDataSize, sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength);
extraDataSize += sPropRecords[i].sPropLength;
}
m_decoderContext->extradata_size = extraDataSize;
之后就不需要每次都给frame提供sps和pps数据了,只需要起始码就可以了。