树莓派使用python-librtmp实现rtmp推流h264的方法


Posted in Python onJuly 22, 2019

目的是能使用Python进行rtmp推流,方便在h264帧里加入弹幕等操作。

librtmp使用的是0.3.0,使用树莓派noir官方摄像头适配的。

通过wireshark抓ffmpeg的包一点点改动,最终可以在red5和斗鱼上推流了。

没怎么写过python,有不恰当的地方请包涵。

上代码:

# -- coding: utf-8 --
# http://blog.csdn.net/luhanglei
import picamera
import time
import traceback
import ctypes
from librtmp import *
 
global meta_packet
global start_time
 
 
class Writer(): # camera可以通过一个类文件的对象来输出,实现write方法即可
  conn = None # rtmp连接
  sps = None # 记录sps帧,发过以后就不需要再发了(抓包看到ffmpeg是这样的)
  pps = None # 同上
  sps_len = 0 # 同上
  pps_len = 0 # 同上
 
  time_stamp = 0
 
  def __init__(self, conn):
    self.conn = conn
 
  def write(self, data):
    try:
      # 寻找h264帧间隔符
      indexs = []
      index = 0
      data_len = len(data)
      while index < data_len - 3:
        if ord(data[index]) == 0x00 and ord(data[index + 1]) == 0x00 and ord(
            data[index + 2]) == 0x00 and ord(data[index + 3]) == 0x01:
          indexs.append(index)
          index = index + 3
        index = index + 1
      # 寻找h264帧间隔符 完成
      # 通过间隔符个数确定类型,树莓派摄像头的第一帧是sps+pps同时发的
      if len(indexs) == 1: # 非sps pps帧
        buf = data[4: len(data)] # 裁掉原来的头(00 00 00 01),把帧内容拿出来
        buf_len = len(buf)
        type = ord(buf[0]) & 0x1f
        if type == 0x05: # 关键帧,根据wire shark抓包结果,需要拼装sps pps 帧内容 三部分,长度都用4个字节表示
          body0 = 0x17
          data_body_array = [bytes(bytearray(
            [body0, 0x01, 0x00, 0x00, 0x00, (self.sps_len >> 24) & 0xff, (self.sps_len >> 16) & 0xff,
             (self.sps_len >> 8) & 0xff,
             self.sps_len & 0xff])), self.sps,
            bytes(bytearray(
              [(self.pps_len >> 24) & 0xff, (self.pps_len >> 16) & 0xff, (self.pps_len >> 8) & 0xff,
               self.pps_len & 0xff])),
            self.pps,
            bytes(bytearray(
              [(buf_len >> 24) & 0xff, (buf_len >> 16) & 0xff, (buf_len >> 8) & 0xff, (buf_len) & 0xff])),
            buf
          ]
          mbody = ''.join(data_body_array)
          time_stamp = 0 # 第一次发出的时候,发时间戳0,此后发真时间戳
          if self.time_stamp != 0:
            time_stamp = int((time.time() - start_time) * 1000)
          packet_body = RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_LARGE, channel=0x06,
                       timestamp=time_stamp, body=mbody)
          packet_body.packet.m_nInfoField2 = 1
        else: # 非关键帧
          body0 = 0x27
          data_body_array = [bytes(bytearray(
            [body0, 0x01, 0x00, 0x00, 0x00, (buf_len >> 24) & 0xff, (buf_len >> 16) & 0xff,
             (buf_len >> 8) & 0xff,
             (buf_len) & 0xff])), buf]
          mbody = ''.join(data_body_array)
          # if (self.time_stamp == 0):
          self.time_stamp = int((time.time() - start_time) * 1000)
          packet_body = RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_MEDIUM, channel=0x06,
                       timestamp=self.time_stamp, body=mbody)
        self.conn.send_packet(packet_body)
      elif len(indexs) == 2: # sps pps帧
        if self.sps is not None:
          return
        data_body_array = [bytes(bytearray([0x17, 0x00, 0x00, 0x00, 0x00, 0x01]))]
        sps = data[indexs[0] + 4: indexs[1]]
        sps_len = len(sps)
        pps = data[indexs[1] + 4: len(data)]
        pps_len = len(pps)
        self.sps = sps
        self.sps_len = sps_len
        self.pps = pps
        self.pps_len = pps_len
        data_body_array.append(sps[1:4])
        data_body_array.append(bytes(bytearray([0xff, 0xe1, (sps_len >> 8) & 0xff, sps_len & 0xff])))
        data_body_array.append(sps)
        data_body_array.append(bytes(bytearray([0x01, (pps_len >> 8) & 0xff, pps_len & 0xff])))
        data_body_array.append(pps)
        data_body = ''.join(data_body_array)
        body_packet = RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_LARGE, channel=0x06,
                     timestamp=0, body=data_body)
        body_packet.packet.m_nInfoField2 = 1
 
        self.conn.send_packet(meta_packet, queue=True)
        self.conn.send_packet(body_packet, queue=True)
    except Exception, e:
      traceback.print_exc()
 
  def flush(self):
    pass
 
 
def get_property_string(string): # 返回两字节string长度及string
  length = len(string)
  return ''.join([chr((length >> 8) & 0xff), chr(length & 0xff), string])
 
 
def get_meta_string(string): # 按照meta packet要求格式返回bytes,带02前缀
  return ''.join([chr(0x02), get_property_string(string)])
 
 
def get_meta_double(db):
  nums = [0x00]
  fp = ctypes.pointer(ctypes.c_double(db))
  cp = ctypes.cast(fp, ctypes.POINTER(ctypes.c_longlong))
  for i in range(7, -1, -1):
    nums.append((cp.contents.value >> (i * 8)) & 0xff)
  return ''.join(bytes(bytearray(nums)))
 
 
def get_meta_boolean(isTrue):
  nums = [0x01]
  if (isTrue):
    nums.append(0x01)
  else:
    nums.append(0x00)
  return ''.join(bytes(bytearray(nums)))
 
 
conn = RTMP(
  'rtmp://192.168.199.154/oflaDemo/test', # 推流地址
  live=True)
librtmp.RTMP_EnableWrite(conn.rtmp)
conn.connect()
start_time = time.time()
# 拼装视频格式的数据包
meta_body_array = [get_meta_string('@setDataFrame'), get_meta_string('onMetaData'),
          bytes(bytearray([0x08, 0x00, 0x00, 0x00, 0x06])), # 两个字符串和ECMA array头,共计6个元素,注释掉了音频相关数据
          get_property_string('width'), get_meta_double(640.0),
          get_property_string('height'), get_meta_double(480.0),
          get_property_string('videodatarate'), get_meta_double(0.0),
          get_property_string('framerate'), get_meta_double(25.0),
          get_property_string('videocodecid'), get_meta_double(7.0),
          # get_property_string('audiodatarate'), get_meta_double(125.0),
          # get_property_string('audiosamplerate'), get_meta_double(44100.0),
          # get_property_string('audiosamplesize'), get_meta_double(16.0),
          # get_property_string('stereo'), get_meta_boolean(True),
          # get_property_string('audiocodecid'), get_meta_double(10.0),
          get_property_string('encoder'), get_meta_string('Lavf57.56.101'),
          bytes(bytearray([0x00, 0x00, 0x09]))
          ]
meta_body = ''.join(meta_body_array)
print meta_body.encode('hex')
meta_packet = RTMPPacket(type=PACKET_TYPE_INFO, format=PACKET_SIZE_LARGE, channel=0x04,
             timestamp=0, body=meta_body)
meta_packet.packet.m_nInfoField2 = 1 # 修改stream id
stream = conn.create_stream(writeable=True)
with picamera.PiCamera() as camera:
  camera.start_preview()
  time.sleep(2)
  camera.start_recording(Writer(conn), format='h264', resize=(640, 480), intra_period=25,
              quality=25) # 开始录制,数据输出到Writer的对象里
  while True:#永远不停止
    time.sleep(60)
  camera.stop_recording()
  camera.stop_preview()

以上这篇树莓派使用python-librtmp实现rtmp推流h264的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python的Asyncore异步Socket模块及实现端口转发的例子
Jun 14 Python
Python 数据结构之队列的实现
Jan 22 Python
python中闭包Closure函数作为返回值的方法示例
Dec 17 Python
Python通过属性手段实现只允许调用一次的示例讲解
Apr 21 Python
Python走楼梯问题解决方法示例
Jul 25 Python
Python中安装easy_install的方法
Nov 18 Python
Python实现二叉树前序、中序、后序及层次遍历示例代码
May 18 Python
Python占用的内存优化教程
Jul 28 Python
Python实现捕获异常发生的文件和具体行数
Apr 25 Python
tensorflow转换ckpt为savermodel模型的实现
May 25 Python
python使用selenium爬虫知乎的方法示例
Oct 28 Python
python中数组和列表的简单实例
Mar 25 Python
python实现大文件分割与合并
Jul 22 #Python
cProfile Python性能分析工具使用详解
Jul 22 #Python
python实现大文本文件分割
Jul 22 #Python
python plotly绘制直方图实例详解
Jul 22 #Python
python分割一个文本为多个文本的方法
Jul 22 #Python
在linux系统下安装python librtmp包的实现方法
Jul 22 #Python
django搭建项目配置环境和创建表过程详解
Jul 22 #Python
You might like
php生成随机数或者字符串的代码
2008/09/05 PHP
php中mysql模块部分功能的简单封装
2011/09/30 PHP
php检测网页是否被百度收录的函数代码
2013/10/09 PHP
php实现保存submit内容之后禁止刷新
2014/03/19 PHP
Thinkphp5行为使用方法汇总
2017/12/21 PHP
laravel请求参数校验方法
2019/10/10 PHP
写入cookie的JavaScript代码库 cookieLibrary.js
2009/10/24 Javascript
DOM_window对象属性之--clipboardData对象操作代码
2011/02/03 Javascript
javascript获取元素CSS样式代码示例
2013/11/28 Javascript
js获取某元素的class里面的css属性值代码
2014/01/16 Javascript
table对象中的insertRow与deleteRow使用示例
2014/01/26 Javascript
JavaScript学习笔记之检测客户端类型是(引擎、浏览器、平台、操作系统、移动设备)
2015/12/03 Javascript
在React框架中实现一些AngularJS中ng指令的例子
2016/03/06 Javascript
JavaScript来实现打开链接页面的简单实例
2016/06/02 Javascript
JavaScript地理位置信息API
2016/06/11 Javascript
jquery uploadify如何取消已上传成功文件
2017/02/08 Javascript
简述Angular 5 快速入门
2017/11/04 Javascript
vue组件间通信子与父详解(二)
2017/11/07 Javascript
使用百度地图实现地图网格的示例
2018/02/06 Javascript
JS中offset和匀速动画详解
2018/02/06 Javascript
jQuery实现的简单歌词滚动功能示例
2019/01/07 jQuery
微信小程序生成分享海报方法(附带二维码生成)
2019/03/29 Javascript
vue安装遇到的5个报错及解决方法
2019/06/12 Javascript
layui的面包屑或者表单不显示的解决方法
2019/09/05 Javascript
Python(Tornado)模拟登录小米抢手机
2013/11/12 Python
Python使用chardet判断字符编码
2015/05/09 Python
详解pyenv下使用python matplotlib模块的问题解决
2018/11/29 Python
Python3爬虫之自动查询天气并实现语音播报
2019/02/21 Python
Banana Republic欧盟:美国都市简约风格的代表品牌
2018/05/09 全球购物
口腔工艺技术专业毕业生自荐信
2013/09/27 职场文书
优秀员工个人的自我评价
2013/11/29 职场文书
办公室主任主任岗位责任制
2014/02/11 职场文书
银行服务感言
2014/03/01 职场文书
企业爱心捐款倡议书
2015/04/27 职场文书
vue使用localStorage持久性存储实现评论列表
2022/04/14 Vue.js
Win10 Anaconda安装python-pcl
2022/04/29 Servers