日期:2019.10.11
常见的使用方式是直接拉流成 FLV 文件:
/**
* @brief:
* Test librtmp of pulling streams
*
* Frames from server --> puller --> local file(.flv or .h264 or .aac)
*/
class CTestLibRTMPPuller
{
public:
CTestLibRTMPPuller();
virtual ~CTestLibRTMPPuller();
bool create(const std::string &file);
void destroy();
bool connect(const std::string &url, uint32_t timeout_secs);
void disconnect();
static void thread_proc(void *param)
{
CTestLibRTMPPuller *this_ptr = (CTestLibRTMPPuller *)param;
if (NULL != this_ptr)
this_ptr->thread_proc_internal();
}
void thread_proc_internal();
protected:
bool _init_sockets();
void _cleanup_sockets();
protected:
RTMP *_rtmp_ptr;
bool _running;
std::thread *_thread_ptr;
uint32_t _buffer_size;
uint8_t *_buffer_ptr;
FILE *_file_ptr;
};
实现:
CTestLibRTMPPuller::CTestLibRTMPPuller()
{
_rtmp_ptr = NULL;
_running = false;
_thread_ptr = NULL;
_buffer_size = 0;
_buffer_ptr = NULL;
_file_ptr = NULL;
}
CTestLibRTMPPuller::~CTestLibRTMPPuller()
{
}
bool CTestLibRTMPPuller::create(const std::string &file)
{
bool success = false;
do {
if (file.empty())
break;
// Init socket
if (!_init_sockets())
break;
// Librtmp init
_rtmp_ptr = RTMP_Alloc();
if (NULL == _rtmp_ptr)
break;
RTMP_Init(_rtmp_ptr);
// Recv buffers
_buffer_size = 2 * 1024 * 1024; // 2MB
_buffer_ptr = new uint8_t[_buffer_size];
if (NULL == _buffer_ptr)
break;
_file_ptr = fopen(file.c_str(), "wb+");
if (NULL == _file_ptr)
break;
success = true;
} while (false);
if (!success) {
destroy();
}
return success;
}
void CTestLibRTMPPuller::destroy()
{
if (NULL != _rtmp_ptr) {
RTMP_Free(_rtmp_ptr);
_rtmp_ptr = NULL;
}
if (NULL != _buffer_ptr) {
delete[] _buffer_ptr;
_buffer_ptr = NULL;
}
_buffer_size = 0;
if (NULL != _file_ptr) {
fclose(_file_ptr);
_file_ptr = NULL;
}
_cleanup_sockets();
}
bool CTestLibRTMPPuller::connect(const std::string &url, uint32_t timeout_secs)
{
bool success = false;
do {
// Parse rtmp url
_rtmp_ptr->Link.timeout = timeout_secs;
_rtmp_ptr->Link.lFlags |= RTMP_LF_LIVE;
if (RTMP_SetupURL(_rtmp_ptr, (char *)url.c_str()) < 0)
break;
// Set recv buffers
RTMP_SetBufferMS(_rtmp_ptr, 2 * 1024 * 1024); // 2MB
// Socket connection
// Handshakes and connect command
if (RTMP_Connect(_rtmp_ptr, NULL) < 0)
break;
// Setup stream and stream settings
if (RTMP_ConnectStream(_rtmp_ptr, 0) < 0)
break;
// Recving thread
_running = true;
_thread_ptr = new std::thread(thread_proc, this);
if (NULL == _thread_ptr)
break;
success = true;
} while (false);
if (!success) {
disconnect();
}
return success;
}
void CTestLibRTMPPuller::disconnect()
{
_running = false;
if (NULL != _thread_ptr) {
_thread_ptr->join();
delete _thread_ptr;
_thread_ptr = NULL;
}
if (NULL != _rtmp_ptr) {
RTMP_Close(_rtmp_ptr);
}
}
void CTestLibRTMPPuller::thread_proc_internal()
{
uint64_t frame_count = 0;
RTMPPacket packet = { 0 };
while (_running)
{
// FLV file
int ret = RTMP_Read(_rtmp_ptr, (char *)_buffer_ptr, _buffer_size);
if (ret < 0)
break;
if (ret == 0) // Timeout for recv
continue;
fwrite(_buffer_ptr, sizeof(uint8_t), ret, _file_ptr);
frame_count++;
if (frame_count % 100 == 0) {
printf("read frames=%lld\n", frame_count);
}
}
}
bool CTestLibRTMPPuller::_init_sockets()
{
WORD version;
WSADATA wsaData;
version = MAKEWORD(2, 2);
return (0 == WSAStartup(version, &wsaData));
}
void CTestLibRTMPPuller::_cleanup_sockets()
{
WSACleanup();
}
RTMP_Read
直接将拉下来的流打包成FLV文件,可以使用播放器直接播放;对于需要H264和AAC流做后续处理的情况不适合使用 RTMP_Read
,可以使用 RTMP_ReadPacket
方式:
// Parse rtmp stream to h264 and aac
uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 };
int ret = RTMP_ReadPacket(_rtmp_ptr, &packet);
if (ret < 0)
break;
if (0 == ret)
continue;
if (RTMPPacket_IsReady(&packet)) {
// Process packet, eg: set chunk size, set bw, ...
RTMP_ClientPacket(_rtmp_ptr, &packet);
if (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) {
bool keyframe = 0x17 == packet.m_body[0] ? true : false;
bool sequence = 0x00 == packet.m_body[1];
printf("keyframe=%s, sequence=%s\n", keyframe ? "true" : "false", sequence ? "true" : "false");
// SPS/PPS sequence
if (sequence) {
uint32_t offset = 10;
uint32_t sps_num = packet.m_body[offset++] & 0x1f;
for (int i = 0; i < sps_num; i++) {
uint8_t ch0 = packet.m_body[offset];
uint8_t ch1 = packet.m_body[offset + 1];
uint32_t sps_len = ((ch0 << 8) | ch1);
offset += 2;
// Write sps data
fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
fwrite(packet.m_body + offset, sizeof(uint8_t), sps_len, _file_ptr);
offset += sps_len;
}
uint32_t pps_num = packet.m_body[offset++] & 0x1f;
for (int i = 0; i < pps_num; i++) {
uint8_t ch0 = packet.m_body[offset];
uint8_t ch1 = packet.m_body[offset + 1];
uint32_t pps_len = ((ch0 << 8) | ch1);
offset += 2;
// Write pps data
fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
fwrite(packet.m_body + offset, sizeof(uint8_t), pps_len, _file_ptr);
offset += pps_len;
}
}
// Nalu frames
else {
uint32_t offset = 5;
uint8_t ch0 = packet.m_body[offset];
uint8_t ch1 = packet.m_body[offset + 1];
uint8_t ch2 = packet.m_body[offset + 2];
uint8_t ch3 = packet.m_body[offset + 3];
uint32_t data_len = ((ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3);
offset += 4;
// Write nalu data(already started with '0x00,0x00,0x00,0x01')
//fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);
fwrite(packet.m_body + offset, sizeof(uint8_t), data_len, _file_ptr);
offset += data_len;
}
}
else if (packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) {
bool sequence = 0x00 == packet.m_body[1];
printf("sequence=%s\n", sequence ? "true" : "false");
// AAC sequence
if (sequence) {
format = (packet.m_body[0] & 0xf0) >> 4;
samplerate = (packet.m_body[0] & 0x0c) >> 2;
sampledepth = (packet.m_body[0] & 0x02) >> 1;
type = packet.m_body[0] & 0x01;
// sequence = packet.m_body[1];
// AAC(AudioSpecificConfig)
if (format == 10) {
uint8_t ch0 = packet.m_body[2];
uint8_t ch1 = packet.m_body[3];
uint16_t config = ((ch0 << 8) | ch1);
object_type = (config & 0xF800) >> 11;
sample_frequency_index = (config & 0x0780) >> 7;
channels = (config & 0x78) >> 3;
frame_length_flag = (config & 0x04) >> 2;
depend_on_core_coder = (config & 0x02) >> 1;
extension_flag = config & 0x01;
}
// Speex(Fix data here, so no need to parse...)
else if (format == 11) {
// 16 KHz, mono, 16bit/sample
type = 0;
sampledepth = 1;
samplerate = 4;
}
}
// Audio frames
else {
// ADTS(7 bytes) + AAC data
uint32_t data_len = packet.m_nBodySize - 2 + 7;
uint8_t adts[7];
adts[0] = 0xff;
adts[1] = 0xf1;
adts[2] = ((object_type - 1) << 6) | (sample_frequency_index << 2) | (channels >> 2);
adts[3] = ((channels & 3) << 6) + (data_len >> 11);
adts[4] = (data_len & 0x7FF) >> 3;
adts[5] = ((data_len & 7) << 5) + 0x1F;
adts[6] = 0xfc;
// Write audio frames
fwrite(adts, sizeof(uint8_t), 7, fp);
fwrite(packet.m_body + 2, sizeof(uint8_t), packet.m_nBodySize - 2, fp);
}
}
else if (packet.m_packetType == RTMP_PACKET_TYPE_INFO) {
// TODO:
// ...
}
else {
// TODO:
// ...
}
}
RTMP_ReadPacket
会自动组包,组包完成可以使用 RTMPPacket_IsReady
判断;RTMP_ClientPacket
做内部相应处理;0x00,0x00,0x00,0x01
分隔符,因此无需额外添加,但解析出的SPS和PPS前需要添加;实际测试时遇到 RTMP_ReadPacket 崩溃的情况(不同URL情况不同),版本为librtmp2.4,VS2017。不知是编译出库的问题还是源码本身bug,欢迎留言交流。
此处只是提供思路,具体优化需要根据应用需求深入研究。
reference:
librtmp获取视频流和音频流1