树莓派使用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使用xlrd实现检索excel中某列含有指定字符串记录的方法
May 09 Python
详解Python的Django框架中的模版继承
Jul 16 Python
用Python的Django框架来制作一个RSS阅读器
Jul 22 Python
python轻松查到删除自己的微信好友
Jan 10 Python
Python使用爬虫猜密码
Feb 19 Python
python3处理含有中文的url方法
May 10 Python
Python面向对象程序设计之继承与多继承用法分析
Jul 13 Python
TensorFlow 输出checkpoint 中的变量名与变量值方式
Feb 11 Python
Python爬取微信小程序Charles实现过程图解
Sep 29 Python
Django项目创建及管理实现流程详解
Oct 13 Python
python如何获得list或numpy数组中最大元素对应的索引
Nov 16 Python
python实现的人脸识别打卡系统
May 08 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环境配置 php5 MySQL5 apache2 phpmyadmin安装与配置图文教程
2007/03/16 PHP
解析func_num_args与func_get_args函数的使用
2013/06/24 PHP
PHP输入流php://input实例讲解
2015/12/22 PHP
Thinkphp 框架扩展之驱动扩展实例分析
2020/04/27 PHP
jquery 学习之二 属性相关
2010/11/23 Javascript
判断对象是否Window的实现代码
2012/01/10 Javascript
通过上下左右键和回车键切换光标实现代码
2013/03/08 Javascript
js实现控制textarea输入字符串的个数,鼠标按下抬起判断输入字符数
2016/10/25 Javascript
js实现刷新页面后回到记录时滚动条的位置【两种方案可选】
2016/12/12 Javascript
JS实现简单的二元方程计算器功能示例
2017/01/03 Javascript
js代码延迟一定时间后执行一个函数的实例
2017/02/15 Javascript
nuxt.js 缓存实践
2018/06/25 Javascript
JavaScript使用prototype原型实现的封装继承多态示例
2018/08/31 Javascript
bootstrap datepicker的基本使用教程
2019/07/09 Javascript
利用d3.js实现蜂巢图表带动画效果
2019/09/03 Javascript
jQuery带控制按钮轮播图插件
2020/07/31 jQuery
详解vue 中 scoped 样式作用域的规则
2020/09/14 Javascript
python自动格式化json文件的方法
2015/03/11 Python
python使用BeautifulSoup分页网页中超链接的方法
2015/04/04 Python
python定时器(Timer)用法简单实例
2015/06/04 Python
python+matplotlib演示电偶极子实例代码
2018/01/12 Python
Django处理文件上传File Uploads的实例
2018/05/28 Python
Python3.6中Twisted模块安装的问题与解决
2019/04/15 Python
django query模块
2019/04/20 Python
Python Django Cookie 简单用法解析
2019/08/13 Python
浅析python标准库中的glob
2020/03/13 Python
如何基于pandas读取csv后合并两个股票
2020/09/25 Python
用css3实现转换过渡和动画效果
2020/03/13 HTML / CSS
canvas三角函数模拟水波效果的示例代码
2018/07/03 HTML / CSS
意大利奢侈品多品牌集合店:TheDoubleF
2019/08/24 全球购物
人力资源管理专业毕业生推荐信
2013/11/07 职场文书
优秀中职教师事迹材料
2014/08/26 职场文书
交通安全横幅标语
2014/10/07 职场文书
毕业设计论文致谢词
2015/05/14 职场文书
2016年“11.11”光棍节活动总结
2016/04/05 职场文书
git stash(储藏)的用法总结
2022/06/25 Servers