树莓派使用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类定义的讲解
Nov 01 Python
用Python编写分析Python程序性能的工具的教程
Apr 01 Python
python爬虫获取淘宝天猫商品详细参数
Jun 23 Python
python快排算法详解
Mar 04 Python
python读取.mat文件的数据及实例代码
Jul 12 Python
django 捕获异常和日志系统过程详解
Jul 18 Python
Django 全局的static和templates的使用详解
Jul 19 Python
DataFrame.to_excel多次写入不同Sheet的实例
Dec 02 Python
pyecharts调整图例与各板块的位置间距实例
May 16 Python
Python用类实现扑克牌发牌的示例代码
Jun 01 Python
Python基于pillow库实现生成图片水印
Sep 14 Python
python3代码输出嵌套式对象实例详解
Dec 03 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设计模式之工厂模式与单例模式
2016/09/28 PHP
php实现头像上传预览功能
2017/04/27 PHP
Laravel6.0.4中将添加计划任务事件的方法步骤
2019/10/15 PHP
checkbox全选所涉及到的知识点介绍
2013/12/31 Javascript
JavaScript数组前面插入元素的方法
2015/04/06 Javascript
jQuery基于ajax实现带动画效果无刷新柱状图投票代码
2015/08/10 Javascript
jQuery选择器总结之常用元素查找方法
2016/08/04 Javascript
谈谈PHP中相对路径的问题与绝对路径的使用
2016/08/16 Javascript
全面解析node 表单的图片上传
2016/11/21 Javascript
Bootstrap Table使用心得总结
2016/11/29 Javascript
自适应布局meta标签中viewport、content、width、initial-scale、minimum-scale、maximum-scale总结
2017/08/18 Javascript
jQuery UI 实例讲解 - 日期选择器(Datepicker)
2017/09/18 jQuery
vue.js分页中单击页码更换页面内容的方法(配合spring springmvc)
2018/02/10 Javascript
Vue 配合eiement动态路由,权限验证的方法
2018/09/26 Javascript
extract-text-webpack-plugin用法详解
2019/02/14 Javascript
简介Python中用于处理字符串的center()方法
2015/05/18 Python
在Django框架中编写Contact表单的教程
2015/07/17 Python
Python中常见的数据类型小结
2015/08/29 Python
解决Python requests 报错方法集锦
2017/03/19 Python
Python编程实现粒子群算法(PSO)详解
2017/11/13 Python
python求前n个阶乘的和实例
2020/04/02 Python
解决pyPdf和pyPdf2在合并pdf时出现异常的问题
2020/04/03 Python
Python读取配置文件(config.ini)以及写入配置文件
2020/04/08 Python
网页布局中CSS样式无效的十个重要原因详解
2017/08/10 HTML / CSS
adidas旗下高尔夫装备供应商:TaylorMade Golf(泰勒梅高尔夫)
2016/08/28 全球购物
新西兰Bookabach:查找全球度假屋
2020/12/03 全球购物
Web Service面试题:如何搭建Axis2的开发环境
2012/06/20 面试题
介绍一下OSI七层模型
2012/07/03 面试题
岗位职责风险点
2014/03/12 职场文书
班主任新年寄语
2014/04/04 职场文书
经贸日语专业个人求职信范文
2014/04/29 职场文书
竞选班干部演讲稿500字
2014/08/20 职场文书
学校政风行风评议心得体会
2014/10/21 职场文书
《小蝌蚪找妈妈》教学反思
2016/02/23 职场文书
python自动化之如何利用allure生成测试报告
2021/05/02 Python
vue项目多环境配置(.env)的实现
2021/07/21 Vue.js