通过libx246 libfaac转换推送RTMP音视频直播流

一、RTMP简介及rtmplib库:

        RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing)和分包(packetizing)的问题。

简介:

  1. RTMP是应用层协议,采用(通常)TCP来保证可靠传输;
  2. 在TCP完成握手链接建立后,还会进行RTMP的一些自己的握手动作,在建立RTMP Connection链接;
  3. 在Connection链接上会传输一些控制信息,例如SetChunkSize,SetACKWindowSize。CreateStream命令会创建一个Stream链接,用于传输具体的音视频数据和控制这些信息传输的命令信息;
  4. RTMP在发送的时候会将数据格式化为Message,然后再将一个个Messsage分为带有Message ID 的Chunk(块),每个Chuck可能是一个完整的Message,可能是一个Message的一部分。在接收端会根据Message ID,长度与data将chunk还原为Message。

        雷霄骅的关于rtmp及rtmplib源码解析的地址。

rtmplib:

        关于外网的RTMPDump文档介绍了rtmp。

RTMPLIB库中重要方法:

  • RTMP_Alloc ():创建会话句柄;
  • RTMP_Init ():初始化rtmp;
  • RTMP_SetupURL():设置URL;
  • RTMP_Connect():建立网络连接;
  • RTMP_ConnectStream():建立连接rtmp流会话;
  • RTMP_Read():读取rtmp流,当返回0字节时,流已经完成。
  • RTMP_Write():发布流,客户端可以在RTMP_Connect () 调用之前 调用RTMP_EnableWrite () 发布流,然后在建立会话后使用 RTMP_Write ();
  • RTMP_Pause():在播放流时,暂停和取消暂停;
  • RTMP_Seek():移动流播放位置;
  • RTMP_Close():关闭流;
  • RTMP_Free():释放会话句柄;
  • RTMPPacket_Alloc():初始化RTMPPacket的内存空间;
  • RTMPPacket:RTMP协议封包;
  • RTMPPacket_Free():释放RTMPPacket的内存空间;

二、X264库简介:

        x264库中重要方法:

  • x264_param_default():给各个参数设置默认值;
  • x264_param_default_preset():设置默认的preset,内部调用了x264_param_apply_preset()和x264_param_apply_tune(),在它们之中即可找到各个preset和tune的详细参数区别;
  • x264_param_apply_profile():给定的文件配置,流的速率;
  • x264_encoder_open():用于打开编码器,其中初始化了libx264编码所需要的各种变量,必须使用x264_encoder_close()进行释放;
  • x264_picture_alloc():为图片开辟数据空间,必须使用x264_picture_clean()进行释放;
  • x264_encoder_encode():编码一帧YUV为H.264码流

        雷霄骅关于X264的源码分析。

        

三、FAAC库简介:

        FAAC是一个MPEG-4和MPEG-2的AAC编码器,其特性是:可移植性好,快速,支持LC/Main/LTP,通过Dream支持DRM,代码小相对于FFMPEG的AAC转码。

        libFaac库中重要方法:

  •  faacEncOpen():打开编码器;
  • faacEncGetCurrentConfiguration():获取配置;
  • faacEncSetConfiguration() :设置配置;
  • faacEncGetDecoderSpecificInfo():得到解码信息;
  • faacEncEnccode():对帧进行编码,并返回编码后的长度。

四、代码框架图及总览:

        Android层获取对应的视频(YUV)及音频(PCM)数据,传入C++层分别通过libx264把YUV转成h264数据、通过libfaac把PCM转成AAC数据。然后包装h264/pcm成RTMPPacket封装包,回调push到PacketQueue队列中,调到子线程RTMP_SendPacket     

五、代码实现细节解析:

        1、LiveManger.java类中获取CamerX视频YUV数据:

private void updateVideoCodecInfo(int degree) {camera2Helper.updatePreviewDegree(degree);if (mRtmpLivePusher != null) {int width = previewSize.getWidth();int height = previewSize.getHeight();if (degree == 90 || degree == 270) {int temp = width;width = height;height = temp;}mRtmpLivePusher.setVideoCodecInfo(width, height, videoFrameRate, videoBitRate);}}@Overridepublic void onPreviewFrame(byte[] yuvData) {
//        Log.e(TAG, "onPreviewFrame:"+yuvData.length);if (yuvData != null && isLiving && mRtmpLivePusher != null) {mRtmpLivePusher.pushVideoData(yuvData);}}

        2、VideoStreamPacket.cpp中设置libx264的设置属性及把YUV封装成H264:

        

void VideoStreamPacket::encodeVideo(int8_t *data) {
//    callbackStatusMsg("VideoStreamPacket encodeVideo",0);lock_guard<mutex> lock(m_mutex);if (!pic_in)return;//YUV420解析分离int offset = 0;memcpy(pic_in->img.plane[0], data, (size_t) m_frameLen); // yoffset += m_frameLen;memcpy(pic_in->img.plane[1], data + offset, (size_t) m_frameLen / 4); // uoffset += m_frameLen / 4;memcpy(pic_in->img.plane[2], data + offset, (size_t) m_frameLen / 4);  //v//YUV封装成H264x264_nal_t *pp_nal;int pi_nal;x264_picture_t pic_out;x264_encoder_encode(videoCodec, &pp_nal, &pi_nal, pic_in, &pic_out);int pps_len, sps_len = 0;uint8_t sps[100] = {0};uint8_t pps[100] = {0};//H264包装成RTMP流格式for (int i = 0; i < pi_nal; ++i) {x264_nal_t nal = pp_nal[i];if (nal.i_type == NAL_SPS) {sps_len = nal.i_payload - 4;memcpy(sps, nal.p_payload + 4, static_cast<size_t>(sps_len));} else if (nal.i_type == NAL_PPS) {pps_len = nal.i_payload - 4;memcpy(pps, nal.p_payload + 4, static_cast<size_t>(pps_len));sendSpsPps(sps, pps, sps_len, pps_len);} else {sendFrame(nal.i_type, nal.p_payload, nal.i_payload);}}}void VideoStreamPacket::encodeVideo(int8_t *data) {
//    callbackStatusMsg("VideoStreamPacket encodeVideo",0);lock_guard<mutex> lock(m_mutex);if (!pic_in)return;//YUV420解析分离int offset = 0;memcpy(pic_in->img.plane[0], data, (size_t) m_frameLen); // yoffset += m_frameLen;memcpy(pic_in->img.plane[1], data + offset, (size_t) m_frameLen / 4); // uoffset += m_frameLen / 4;memcpy(pic_in->img.plane[2], data + offset, (size_t) m_frameLen / 4);  //v//YUV封装成H264x264_nal_t *pp_nal;int pi_nal;x264_picture_t pic_out;x264_encoder_encode(videoCodec, &pp_nal, &pi_nal, pic_in, &pic_out);int pps_len, sps_len = 0;uint8_t sps[100] = {0};uint8_t pps[100] = {0};//H264包装成RTMP流格式for (int i = 0; i < pi_nal; ++i) {x264_nal_t nal = pp_nal[i];if (nal.i_type == NAL_SPS) {sps_len = nal.i_payload - 4;memcpy(sps, nal.p_payload + 4, static_cast<size_t>(sps_len));} else if (nal.i_type == NAL_PPS) {pps_len = nal.i_payload - 4;memcpy(pps, nal.p_payload + 4, static_cast<size_t>(pps_len));sendSpsPps(sps, pps, sps_len, pps_len);} else {sendFrame(nal.i_type, nal.p_payload, nal.i_payload);}}}

        3、LiveManger.java中获取AudioRecord的PCM数据:

 private void initAudio() {int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig, audioFormat) * 2;int bufferSizeInBytes = Math.max(minBufferSize, mRtmpLivePusher.getInputSamplesFromNative());if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {// TODO: Consider calling//    ActivityCompat#requestPermissions// here to request the missing permissions, and then overriding//   public void onRequestPermissionsResult(int requestCode, String[] permissions,//                                          int[] grantResults)// to handle the case where the user grants the permission. See the documentation// for ActivityCompat#requestPermissions for more details.return;}mRtmpLivePusher.setAudioCodecInfo(sampleRate, channelConfig);inputSamples = mRtmpLivePusher.getInputSamplesFromNative();mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate,channelConfig, audioFormat, bufferSizeInBytes);}class AudioTask implements Runnable {@Overridepublic void run() {mAudioRecord.startRecording();byte[] bytes = new byte[inputSamples];while (isLiving) {int len = mAudioRecord.read(bytes, 0, bytes.length);if (len > 0) {mRtmpLivePusher.pushAudioData(bytes);}}mAudioRecord.stop();}}

        4、AudioStreamPacket.cpp中设置libfaac的属性及把PCM封装成AAC:

int AudioStreamPacket::setAudioEncInfo(int samplesInHZ, int channels) {callbackStatusMsg("AudioStreamPacket setAudioEncInfo", 0);m_channels = channels;//open faac encoderm_audioCodec = faacEncOpen(static_cast<unsigned long>(samplesInHZ),static_cast<unsigned int>(channels),&m_inputSamples,&m_maxOutputBytes);m_buffer = new u_char[m_maxOutputBytes];//set encoder paramsfaacEncConfigurationPtr config = faacEncGetCurrentConfiguration(m_audioCodec);config->mpegVersion = MPEG4;    //设置版本,录制MP4文件时要用MPEG4config->aacObjectType = LOW;     //编码类型config->inputFormat = FAAC_INPUT_16BIT;     //输入数据类型config->outputFormat = 0;return faacEncSetConfiguration(m_audioCodec, config);
}void AudioStreamPacket::encodeData(int8_t *data) {
//    callbackStatusMsg("AudioStreamPacket encodeData", 0);//encode a frame, and return encoded lenint byteLen = faacEncEncode(m_audioCodec, reinterpret_cast<int32_t *>(data),static_cast<unsigned int>(m_inputSamples),m_buffer,static_cast<unsigned int>(m_maxOutputBytes));if (byteLen > 0) {int bodySize = 2 + byteLen;auto *packet = new RTMPPacket();RTMPPacket_Alloc(packet, bodySize);//stereopacket->m_body[0] = 0xAF;if (m_channels == 1) {packet->m_body[0] = 0xAE;}packet->m_body[1] = 0x01;memcpy(&packet->m_body[2], m_buffer, static_cast<size_t>(byteLen));packet->m_hasAbsTimestamp = 0;packet->m_nBodySize = bodySize;packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;packet->m_nChannel = 0x11;packet->m_headerType = RTMP_PACKET_SIZE_LARGE;if (mContext != nullptr && mAudioCallback != nullptr) {mAudioCallback(mContext, packet);}}
}

        5、RtmpPusherManger.cpp分别把AudioStreamPacket.cpp和VideoStreamPacket.cpp回调的RTMPPacket包,然后传入到RtmpInit.cpp。包装的RTMPPacket包push到队列PacketQueue中,

void RtmpPusherManger::initVideoPacket() {RtmpStatusMessage(this, "initVideoPacket", 0);if (videoStreamPacket == nullptr) {videoStreamPacket = new VideoStreamPacket();}videoStreamPacket->setRtmpStatusCallback(this, RtmpStatusMessage);videoStreamPacket->setVideoCallback(this, callbackRtmpPacket);
}void RtmpPusherManger::initAudioPacket() {RtmpStatusMessage(this, "initAudioPacket", 0);if (audioStreamPacket == nullptr) {audioStreamPacket = new AudioStreamPacket();}audioStreamPacket->setRtmpStatusCallback(this, RtmpStatusMessage);audioStreamPacket->setAudioCallback(this, callbackRtmpPacket);
}void RtmpPusherManger::callbackRtmpPacket(void *context, RTMPPacket *packet) {if (context != nullptr && packet != nullptr) {RtmpPusherManger *pFmpegManger = static_cast<RtmpPusherManger *>(context);pFmpegManger->addRtmpPacket(packet);}}void RtmpPusherManger::addRtmpPacket(RTMPPacket *packet) {if (rtmpInit == nullptr) return;rtmpInit->addRtmpPacket(packet);
}

        6、RtmpInit.cpp中从RtmpPusherManger.cpp传入添加的RTMPPacket包,push到队列PacketQueue中。在子线程中初始化RTMP相关操作成功后不断从PacketQueue中pop出RTMPPacket,并最终把RTMPPacket在RTMP_SendPacket发送出去。

void RtmpInit::addRtmpPacket(RTMPPacket *packet) {
//    callbackStatusMsg("Rtmp addRtmpPacket", 0);packet->m_nTimeStamp = RTMP_GetTime() - start_time;packetQueue.push(packet);
}void RtmpInit::startThread() {callbackStatusMsg("Rtmp startThread", 0);LOGE("murl:%s", mUrl);char *url = const_cast<char *>(mUrl);RTMP *rtmp = 0;do {rtmp = RTMP_Alloc();if (!rtmp) {callbackStatusMsg("Rtmp create fail", -1);break;}RTMP_Init(rtmp);rtmp->Link.timeout = 5;int ret = RTMP_SetupURL(rtmp, url);if (!ret) {callbackStatusMsg("Rtmp SetupURL fail", ret);break;}//开启输出模式RTMP_EnableWrite(rtmp);ret = RTMP_Connect(rtmp, 0);if (!ret) {callbackStatusMsg("rtmp连接地址失败", ret);break;}ret = RTMP_ConnectStream(rtmp, 0);if (!ret) {callbackStatusMsg("rtmp连接流失败", ret);break;}//start pushingisPushing = true;packetQueue.setRunning(true);//获取音频的首帧值if (mContext != nullptr) {mGetAudioTagCallback(mContext);}RTMPPacket *packet = nullptr;while (isPushing) {packetQueue.pop(packet);if (!isPushing) {break;}if (!packet) {continue;}packet->m_nInfoField2 = rtmp->m_stream_id;ret = RTMP_SendPacket(rtmp, packet, 1);releasePackets(packet);if (!ret) {LOGE("RTMP_SendPacket fail...");callbackStatusMsg("RTMP_SendPacket fail...", -2);break;}}releasePackets(packet);} while (0);isPushing = false;packetQueue.setRunning(false);packetQueue.clear();//释放rtmpif (rtmp) {RTMP_Close(rtmp);RTMP_Free(rtmp);}delete url;
}

      7、 Github的项目地址:https://github.com/wangyongyao1989/WyFFmpeg/tree/main/rtmplive

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/379971.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql----内置函数

前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、日期函数 日期&#xff1a;年月日 时间&#xff1a;时分秒 查询&#xff1a;当前时间&#xff0c;只显示当前日期 注意&#xff1a;如果类型为date或者datetime。表中数据类型为date,你插入时…

昇思MindSpore 应用学习-FCN图像语义分割-CSDN

日期 心得 昇思MindSpore 应用学习-FCN图像语义分割 (AI 代码解析) 全卷积网络&#xff08;Fully Convolutional Networks&#xff0c;FCN&#xff09;是UC Berkeley的Jonathan Long等人于2015年在Fully Convolutional Networks for Semantic Segmentation[1]一文中提出的用…

MATLAB R2023b下载安装教程汉化中文版设置

MATLAB R2023b下载安装教程汉化中文版设置 Matlab 是一款功能强大的商业数学软件 Matlab&#xff08;Matrix Labortory&#xff09;即矩阵实验室&#xff0c;它在数值计算、数据分析、算法开发、建模与仿真等众多领域都发挥着重要作用。 Matlab 具有以下显著特点和优势&…

移动端如何离线使用GPT

在移动端离线使用GPT&#xff0c;只需要一个app&#xff1a;H2O AI Personal GPT 是H2OAI上架的一款app&#xff0c;可离线使用&#xff0c;注重数据隐私&#xff0c;所有数据都只存储在本地。对H2OAI感兴趣的伙伴&#xff0c;可移步&#xff1a;https://h2o.ai 该app支持的模…

大语言模型-文本向量模型评估基准 MTEB

MTEB&#xff08;Massive Text Embedding Benchmark&#xff09; 涵盖112种语言的58个数据集&#xff0c;包含如下8种任务。 1、双语文本挖掘&#xff08;Bitext Mining&#xff09; 任务目标&#xff1a; 在双语语料库中识别语义等价的句子对。 任务描述&#xff1a; 输入…

服务器借助笔记本热点WIFI上网

一、同一局域网环境 1、当前环境&#xff0c;已有交换机组网环境&#xff0c;服务器已配置IP信息。 设备ip服务器125.10.100.12交换机125.10.100.0/24笔记本125.10.100.39 2、拓扑图 #mermaid-svg-D4moqMym9i0eeRBm {font-family:"trebuchet ms",verdana,arial,sa…

价格战再起:OpenAI 发布更便宜、更智能的 GPT-4o Mini 模型|TodayAI

OpenAI 今日推出了一款名为 GPT-4o Mini 的新模型&#xff0c;这款模型较轻便且成本更低&#xff0c;旨在为开发者提供一个经济实惠的选择。与完整版模型相比&#xff0c;GPT-4o mini 在成本效益方面表现卓越&#xff0c;价格仅为每百万输入 tokens 15 美分和每百万输出 tokens…

【接口自动化_12课_基于Flask搭建MockServer】

DAY12_基于Flask搭建MockServer 目标&#xff1a;通过本节课主要核心内容要理解什么是MockServer&#xff0c;并且结合Flask进行实战。 章节大纲 1. 什么是Mock及应用场景理解 2. 框架对比及Flask基本应用理解 3. Mock Server接口设计实战重要 4. Mock Server如何运行理解…

守护动物乐园:视频AI智能监管方案助力动物园安全与秩序管理

一、背景分析 近日&#xff0c;某大熊猫参观基地通报了4位游客在参观时&#xff0c;向大熊猫室外活动场内吐口水的不文明行为。这几位游客的行为违反了入园参观规定并可能对大熊猫造成严重危害&#xff0c;已经被该熊猫基地终身禁止再次进入参观。而在此前&#xff0c;另一熊猫…

IMU提升相机清晰度

近期&#xff0c;一项来自北京理工大学和北京师范大学的团队公布了一项创新性的研究成果&#xff0c;他们将惯性测量单元&#xff08;IMU&#xff09;和图像处理算法相结合&#xff0c;显著提升了非均匀相机抖动下图像去模糊的准确性。 研究团队利用IMU捕捉相机的运动数据&…

苹果电脑crossover怎么下载 苹果电脑下载crossover对电脑有影响吗 MacBook下载crossover软件

CodeWeavers 发布了 CrossOver 24 版本更新&#xff0c;不仅兼容更多应用和游戏&#xff0c;得益于 Wine 9.0 带来的 7000 多项改进&#xff0c;CrossOver 还可以在 64 位系统上运行Windows应用的软件&#xff0c;使得用户可以在Mac系统中轻松安装使用仅支持Windows系统运营环境…

GPU租赁教程/云主机使用教程/在线GPU环境部署/免费GPU/免费算力||运用云服务器,跑自己的深度学习模型(保姆级教程)

一、环境准备 pycharm professional&#xff08;需要pycharm专业版&#xff0c;社区版不行&#xff09;潞晨云(潞晨科技)访问链接&#xff0c;目前应该是最便宜的GPU租赁平台了&#xff0c;不知道之后会不会涨价&#xff0c;点我链接注册送10元代金券&#xff0c;能跑6个小时的…

spark 动态资源分配dynamicAllocation

动态资源分配&#xff0c;主要是spark在运行中可以相对合理的分配资源。 初始申请的资源远超实际需要&#xff0c;减少executor初始申请的资源比实际需要少很多&#xff0c;增多executorSpark运行多个job&#xff0c;这些job所需资源有的多有的少&#xff0c;动态调整executor…

微信小程序 button样式设置为图片的方法

微信小程序 button样式设置为图片的方法 background-image background-size与background-repeat与border:none;是button必须的 <view style" position: relative;"><button class"customer-service-btn" style"background-image: url(./st…

Python 合并两个有序数组

Python 合并两个有序数组 正文 正文 题目说明如下&#xff1a; 这里我们直接让 nums1 的后 n 个数等于 nums2 数组&#xff0c;然后对 nums1 数组整体进行排序即可。 class Solution:def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:"…

云原生系列 - Jenkins

Jenkins Jenkins&#xff0c;原名 Hudson&#xff0c;2011 年改为现在的名字。它是一个开源的实现持续集成的软件工具。 官方网站&#xff08;英文&#xff09;&#xff1a;https://www.jenkins.io/ 官方网站&#xff08;中文&#xff09;&#xff1a;https://www.jenkins.io…

网站开发:使用VScode安装yarn包和运行前端项目

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端 输入 npm install -g yarn 再次输入以…

[CP_AUTOSAR]_分层软件架构_接口之通信模块交互介绍

目录 1、协议数据单元(PDU)传输2、通信模块的案例2.1、SDU、 PCI & PDU2.2、通信模块构成2.3、从数据传输的角度看Communication2.4、Communication中的接口 在前面 《关于接口的一些说明》 以及  《Memory软件模块接口说明》 中&#xff0c;简要介绍了CP_AUTOSAR分层…

scp免密复制文件

实现在服务器A和服务器B之间使用scp命令免密互相传输文件 1. 在服务器A中免密复制到服务器B 1.1 生成服务器A的公钥私钥 #在服务器A中执行 ssh-keygen -t rsa -P ""命令执行完毕会在服务器A的 ~/.ssh 目录下生成两个文件&#xff1a;id_rsa 和 id_rsa.pub 1.2 拷…

nodejs下载+react安装

一、nodejs安装 1、nodejs下载 具体安装可参考连接&#xff1a;2023最新版Node.js下载安装及环境配置教程&#xff08;非常详细&#xff09;从零基础入门到精通&#xff0c;看完这一篇就够了_nodejs安装及环境配置-CSDN博客 下载地址&#xff1a;Node.js — 下载 Node.js 测…