若该文为原创文章,转载请注明原文出处。
T31使用librtmp拉流并保存成FLV文件或H264和AAC文件。
librtmp编译在前面有教程,自行编译。
实现的目的是想要获取获取rtmp的AAC流并播放,实时双向对讲功能。
一、硬件和开发环境
1、硬件:T31X+SC5235
2、开发环境: ubuntu16.04-64bit
3、编译器:mips-gcc540-glibc222-32bit-r3.3.0.tar.gz
注:板子和和WIFI模块是某淘上淘的,使用的是RTL8188,使用的是USB接口,uboot和内核是自己裁剪移植的,内核默认自带WIFI驱动,所以不用移植可以直接使用。
二、使用librtmp拉流流程
- 初使化RTMP上下文
- 设置拉流地址
- 连接服务器
- 连接流地址
- 循环拉流,提取媒体数据,保存为文件或者交给解码模块
- 拉流完毕,释放资源
三、代码解析
编译代码需要用到的库,本人在T31上使用的是静态库,可以自行改成动态库,以减少编译文件的大小 。
编译所需的库有:librtmp.a、libssl.a、libcrypto.a、libz.a
下面这个例子演示了使用librtmp库从服务器拉流到本地保存为flv文件或是h264和aac文件,代码如下:
/*!
*****************************************************************************
*
* Copyright ? 2017-2018 yifeng. All Rights Reserved.
*
* \file main.c
* \author yifeng
* \version 1.0
* \date 2022年3月3日
* \brief rtmp测试代码
*
*----------------------------------------------------------------------------
* \attention
*
*
*****************************************************************************
*/
/*****************************************************************************
change history:
1.date : 2022年3月3日
author: yifeng
change: create file
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
/* 环形队列头文件 */
#include "xiecc_rtmp.h"
#include "rtmp.h"
typedef unsigned long ULONG;
typedef unsigned int UINT;
typedef unsigned char BYTE;
typedef char _TCHAR;
uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 };
/*!
* \fn main
* \brief 主函数
*
* \param [in] int argc #
* \param [in] char *argv[] #
*
* \retval int
*/
int main(int argc, char *argv[])
{
uint16_t object_type = 0;
uint16_t sample_frequency_index = 0;
uint8_t channels = 0;
uint8_t frame_length_flag = 0;
uint8_t depend_on_core_coder = 0;
uint8_t extension_flag = 0;
// 初使化RTMP上下文
RTMP* pRTMP = RTMP_Alloc();
RTMP_Init(pRTMP);
// 设置拉流地址
RTMP_SetupURL(pRTMP, (char*)"rtmp://192.168.0.109/live/stream");
// 连接服务器
pRTMP->Link.timeout = 10;
pRTMP->Link.lFlags |= RTMP_LF_LIVE;
bool b = RTMP_Connect(pRTMP, NULL);
if (!b)
{
printf("connect failed! \n");
return -1;
}
// 连接流地址
b = RTMP_ConnectStream(pRTMP, 0);
if (!b)
{
printf("connect stream failed! \n");
return -1;
}
bool bSaveFlv = false; // 保存成FLV格式
FILE *pFile = fopen("testrtmp.flv", "wb");
FILE *h264_file_ptr = fopen("testrtmp.h264", "wb");
FILE *aac_file_ptr = fopen("testrtmp.aac", "wb");
while (RTMP_IsConnected(pRTMP))
{
if (bSaveFlv == true)
{
char sBuf[4096] = {0};
int bytes = RTMP_Read(pRTMP, sBuf, sizeof(sBuf));
printf("RTMP_Read() ret:[%d] \n", bytes);
if (bytes <= 0)
break;
fwrite(sBuf, 1, bytes, pFile);
}
else
{
RTMPPacket packet;
RTMPPacket_Reset(&packet);
packet.m_body = NULL;
packet.m_chunk = NULL;
b = RTMP_ReadPacket(pRTMP, &packet);
if (b < 0)
break;
if (0 == b )
continue;
if (RTMPPacket_IsReady(&packet))
{
RTMP_ClientPacket(pRTMP, &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
printf("Write sps data len: %d\n", sps_len);
fwrite(nalu_header, sizeof(uint8_t), 4, h264_file_ptr);
fwrite(packet.m_body + offset, sizeof(uint8_t), sps_len, h264_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
printf("Write pps data len: %d\n", pps_len);
fwrite(nalu_header, sizeof(uint8_t), 4, h264_file_ptr);
fwrite(packet.m_body + offset, sizeof(uint8_t), pps_len, h264_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')
printf("Write nalu data len: %d\n", data_len);
fwrite(nalu_header, sizeof(uint8_t), 4, h264_file_ptr);
fwrite(packet.m_body + offset, sizeof(uint8_t), data_len, h264_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)
{
uint8_t format = (packet.m_body[0] & 0xf0) >> 4;
uint8_t samplerate = (packet.m_body[0] & 0x0c) >> 2;
uint8_t sampledepth = (packet.m_body[0] & 0x02) >> 1;
uint8_t 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
printf("Write audio frames len: %d\n", packet.m_nBodySize - 2);
fwrite(adts, sizeof(uint8_t), 7, aac_file_ptr);
fwrite(packet.m_body + 2, sizeof(uint8_t), packet.m_nBodySize - 2, aac_file_ptr);
}
}
else if (packet.m_packetType == RTMP_PACKET_TYPE_INFO)
{
// TODO:
// ...
printf("RTMP_PACKET_TYPE_INFO1\n");
}
else
{
// TODO:
// ...
printf("RTMP_PACKET_TYPE_INFO2\n");
}
}
RTMPPacket_Free(&packet);
}
}
fclose(pFile);
RTMP_Close(pRTMP);
RTMP_Free(pRTMP);
return 0;
}
定义了bSaveFlv标记,是否保存成flv文件,true保存成flv文件,否则保存成aac和h264,
rtmp的Url需要根据自己的服务器修改。
四、测试结果
执行后,在当前目录下生成264和aac文件
把文件复制到pc端用vlc播放
使用librtmp拉流网友説会有问题,目前测试是正常,有遇到的网友麻烦告知一下。谢谢。
如有侵权,或需要完整代码,请及时联系博主。