ffmpeg介紹
1 ffserver命令
fserver是一個音頻和視頻的流式服務器。它通過在啟動時讀入的配置文件完成配置,不指定時用默認的/etc/ffserver.conf文件。ffserver接受一些或者FFM流作為輸入然后通過RTP/RTSP/HTTP推流。ffserver監聽在配置文件中指定的端口,在配置文件中輸入的流叫做feed,每一個都是用<Feed>的節來指定的。每一個feed可以有不同格式的的不同輸出流,每一個在配置文件中用<Stream>節來指定。ffserver是通過推送用ffmpeg編碼的流來工作的。ffserver擔當一個HTTPserver的角色,接收來自ffmpeg的獲得發布流的POST請求,並且用流媒體內容來服務HTTP/RTSP客戶端的GET請求。要搞清楚什么是feed,什么是stream。
1.1 Feed是什么
feed是由ffmpeg創建的FFM流,並且發送到ffserver正在監聽的端口上。每一個feed是通過一個唯一的名字來識別的,這個名字關聯到發布在ffserver上的資源的名字,並且是通過配置文件里面的<Feed>節來完成配置的。feed發布的URL是以下面的形式給出的:
http://<ffserver_ip_address>:<http_port>/<feed_name>
其中,ffserver_ip_address是ffserver安裝的主機的IP地址,http_port是HTTP服務器的端口號,feed_name是相關的定義在配置文件中的feed的名字。每一個feed跟一個磁盤上的文件相關聯,這個文件用於當新內容被實時地加入進流的時候,這個文件是用於允許發送預先錄制的數據盡可能快地到一個播放器。
1.2 stream是什么
一個實時流或者一個流是一個由ffserver發布的資源並且通過HTTP協議使客戶可以訪問。一個流可以連接到一個feed上,或者一個文件上。當連接到一個feed上的時候,發布的流是推的來自相關聯的通過運行的ffmpeg的實例所形成的feed上的。在第二種情況中,流是從pre-recorded文件里讀來的。每一個流有一個唯一的名字,關聯到ffserver上的資源的名字,並且是通過配置文件中的復雜的stream節來配置的。訪問流的HTTP地址形式:
http://<ffserver_ip_address>:<http_port>/<stream_name>[<options>]
訪問流的RTSP地址形式如下:
http://<ffserver_ip_address>:<http_port>/<stream_name>[<options>]
其中,stream_name是配置文件中定義的流的名字,options是在URL后面指定的選項的列表,將影響流如何通過ffserver提供的方式。
2 ffmpeg命令
ffmpeg是一個可以從現場的音視頻源中采集的非常快速的視頻和音頻轉換器。可以在任意的采樣率之間轉換,並且可以在現場resize視頻。ffmpeg用-i參數從任意數量的輸入files中讀入,並寫入任意數量的輸出文件(通過普通的輸出文件名指定)。從輸入中選擇哪個流到輸出流是自動完成或者用-map選項指定。為了引用選項中的輸入文件,必須用索引號。類似地,文件里的流也是通過索引號引用的。如2:3引用第3個輸入文件中的第4個流。一般的規則是選項是用在下一個指定的文件上的,因此,順序是很重要的。每出現一個將運用到下一個輸入或者輸出文件上。這個規則的例外是最開始就指定的全局選項。
不要混淆輸入和輸出文件,首先指定完所有的輸入文件,然后才指定輸出文件。也不要混淆屬於不同的文件的選項,所有選項僅僅運用在下一個輸入或輸出文件上,並且在不同的文件之間會重置。
下面來3個實例:
a 設置輸出文件的視頻碼率為64kbps ffmpeg -i input.avi -b:v 64k -bufsize 64k output.avi b 強制輸出文件的幀率為24fps: ffmpeg -i input.avi -r 24 output.avi c 強制輸入文件的幀率為1fps並且輸出文件的幀率為24fps ffmpeg -r 1 -i input.avi -r 24 output.avi
原始輸入文件需要格式化選項。
ffmpeg調用libavformat(包含解封裝)讀入輸入文件並從中取得包含了編碼數據的包。當有多個輸入文件,ffmpeg通過跟蹤最低的時間戳或者跟蹤任何激活的輸入流來保持同步。編碼的包然后傳遞給解碼器(除非指定是拷貝流,那么就不經過解碼以及后面的編碼)。解碼器產生可以被filtering進一步處理的非壓縮幀,在filtering之后,幀傳給encoder(編碼他們並且輸出編碼包),最后,傳給封裝器muxer,把編碼包寫給輸出文件。在編碼之前ffmpeg可以libavfilter庫中的濾波器來處理原始音視頻幀。濾波器鏈產生了濾波器圖,ffmpeg有兩種類型的濾波器圖:簡單的和復雜的。
flv格式詳解
FLV(Flash Video)是現在非常流行的流媒體格式,由於其視頻文件體積輕巧、封裝播放簡單等特點,使其很適合在網絡上進行應用,目前主流的視頻網站無一例外地使用了FLV格式。另外由於當前瀏覽器與Flash Player緊密的結合,使得網頁播放FLV視頻輕而易舉,也是FLV流行的原因之一。
FLV是流媒體封裝格式,我們可以將其數據看為二進制字節流。總體上看,FLV包括文件頭(File Header)和文件體(File Body)兩部分,其中文件體由一系列的Tag及Tag Size對組成。

FLV格式解析
先來一張圖,這是《東風破》——周傑倫(下載)的一個MV視頻。我使用的是Binary Viewer的二進制查看工具。

header
頭部分由一下幾部分組成
Signature(3 Byte)+Version(1 Byte)+Flags(1 Bypte)+DataOffset(4 Byte)
- signature 占3個字節
固定FLV三個字符作為標示。一般發現前三個字符為FLV時就認為他是flv文件。 - Version 占1個字節
標示FLV的版本號。這里我們看到是1 - Flags 占1個字節
內容標示。第0位和第2位,分別表示 video 與 audio 存在的情況.(1表示存在,0表示不存在)。截圖看到是0x05
,也就是00000101
,代表既有視頻,也有音頻。 - DataOffset 4個字節
表示FLV的header長度。這里可以看到固定是9
body
FLV的body部分是由一系列的back-pointers + tag構成
- back-pointers 固定4個字節,表示前一個tag的size。
- tag 分三種類型,video、audio、scripts。
tag組成
tag type
+tag data size
+Timestamp
+TimestampExtended
+stream id
+ tag data
- type 1個字節。8為Audio,9為Video,18為scripts
- tag data size 3個字節。表示tag data的長度。從streamd id 后算起。
- Timestreamp 3個字節。時間戳
- TimestampExtended 1個字節。時間戳擴展字段
- stream id 3個字節。總是0
- tag data 數據部分
我們根據實例來分析:
看到第一個TAG
type=0x12
=18。這里應該是一個scripts。
size=0x000125
=293。長度為293。
timestreamp=0x000000
。這里是scripts,所以為0
TimestampExtended =0x00
。
stream id =0x000000
我們看一下TAG的data部分:

tag的划分
圖中紅色部分是我標出的兩個back-pointers,都是4個字節。而中間就是第一個TAG。那是怎么計算的呢?我們就以這個做個示例。
- 首先第一個back-pointers是
0x00000000
,那是因為后面是第一個TAG。所以他為0。 - 然后根據我們我們前面格式獲取到size是
0x000125
。也就是說從stream id后面再加上293個字節就到了第一個TAG的末尾,我們數一下一下。stream id以前總共有24個字節(9+4+11)。那么到第一個TAG結束,下一個TAG開始的位置是293+24=137=0x13D
。 - 接下來我們找到
0x13D
的地址,從工具上很容易找到,正好就是紅色下划線的前面。紅色部分是0x00000130
=304,這代表的是上一個TAG的大小。 - 最后我們計算一下,上一個TAG數據部分是293個字節,前面type、stream id等字段占了11個字節。正好是匹配的。
上面我們已經知道了怎么取划分每個TAG。接下來我們就看TAG的具體內容
tag的內容
前面已經提到tag分3種。我們一個個看
script
腳本Tag一般只有一個,是flv的第一個Tag,用於存放flv的信息,比如duration、audiodatarate、creator、width等。
首先介紹下腳本的數據類型。所有數據都是以數據類型+(數據長度)+數據的格式出現的,數據類型占1byte,數據長度看數據類型是否存在,后面才是數據。
一般來說,該Tag Data結構包含兩個AMF包。AMF(Action Message Format)是Adobe設計的一種通用數據封裝格式,在Adobe的很多產品中應用,簡單來說,AMF將不同類型的數據用統一的格式來描述。第一個AMF包封裝字符串類型數據,用來裝入一個“onMetaData”標志,這個標志與Adobe的一些API調用有,在此不細述。第二個AMF包封裝一個數組類型,這個數組中包含了音視頻信息項的名稱和值。具體說明如下,大家可以參照圖片上的數據進行理解。
值 | 類型 | 說明 |
---|---|---|
0 | Number type | 8 Bypte Double |
1 | Boolean type | 1 Bypte bool |
2 | String type | 后面2個字節為長度 |
3 | Object type | |
4 | MovieClip type | |
5 | Null type | |
6 | Undefined type | |
7 | Reference type | |
8 | ECMA array type | 數組,類似Map |
10 | Strict array type | |
11 | Date type | |
12 | Long string type | 后面4個字節為長度 |

上圖為第一個AMF包
- type=
0x02
對應String - size=
0A
=10 -
value=onMetaData 正好是10個字節。
-
上圖為第二個AMF
- type=
0x08
對應ECMA array type。
表示數組,類似Map。后面4個字節為數組的個數。然后是鍵值對,第一個為鍵,2個字節為長度。后面跟具體的內容。接着3個字節表示值的類型,然后根據類型判斷長度。
上圖我們可以判斷,總共有13個鍵值對。
第一個長度為8個字節是duration。值類型是0x004073
,第一個字節是00,所以是double,8個字節。
第二個長度5個字節是width。值也是double類型,8個字節。
依次解析下去...
到處,我們已經知道了如何解析FLV中Tag為script的數據。
video

type=0x09
=9。這里應該是一個video。
size=0x000030
=48。長度為48。
timestreamp=0x000000
。
TimestampExtended =0x00
。
stream id =0x000000
我們看到數據部分:
視頻信息+數據
視頻信息,1個字節。
前4位為幀類型Frame Type
值 | 類型 |
---|---|
1 | keyframe (for AVC, a seekable frame) 關鍵幀 |
2 | inter frame (for AVC, a non-seekable frame) |
3 | disposable inter frame (H.263 only) |
4 | generated keyframe (reserved for server use only) |
5 | video info/command frame |
后4位為編碼ID (CodecID)
值 | 類型 |
---|---|
1 | JPEG (currently unused) |
2 | Sorenson H.263 |
3 | Screen video |
4 | On2 VP6 |
5 | On2 VP6 with alpha channel |
6 | Screen video version 2 |
7 | AVC |
特殊情況
視頻的格式(CodecID)是AVC(H.264)的話,VideoTagHeader會多出4個字節的信息,AVCPacketType 和CompositionTime。
- AVCPacketType 占1個字節
值 | 類型 |
---|---|
0 | AVCDecoderConfigurationRecord(AVC sequence header) |
1 | AVC NALU |
2 | AVC end of sequence (lower level NALU sequence ender is not required or supported) |
AVCDecoderConfigurationRecord.包含着是H.264解碼相關比較重要的sps和pps信息,再給AVC解碼器送數據流之前一定要把sps和pps信息送出,否則的話解碼器不能正常解碼。而且在解碼器stop之后再次start之前,如seek、快進快退狀態切換等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情況也是出現1次,也就是第一個video tag.
- CompositionTime 占3個字節
條件 | 值 |
---|---|
AVCPacketType ==1 | Composition time offset |
AVCPacketType !=1 | 0 |
我們看第一個video tag,也就是前面那張圖。我們看到AVCPacketType =0。而后面三個字節也是0。說明這個tag記錄的是AVCDecoderConfigurationRecord。包含sps和pps數據。
再看到第二個video tag

我們看到 AVCPacketType =1,而后面三個字節為000043
。這是一個視頻幀數據。
解析到的數據完全符合上面的理論。
sps pps
前面我們提到第一個video 一般存放的是sps和pps。這里我們具體解析下sps和pps內容。先看下存儲的格式(圖6):
0x01
+sps[1]
+sps[2]
+sps[3]
+0xFF
+0xE1
+sps size
+sps
+01
+pps size
+pps
我們看到圖 。
sps[1]=0x64
sps[2]=00
sps[3]=0D
sps size=0x001B
=27
跳過27個字節后,是0x01
pps size=0x0005
=5
跳過5個字節,就到了back-pointers。
視頻幀數據
解析出sps和pps tag后,后面的video tag就是真正的視頻數據內容了

這是第二個video tag其實和圖8一樣,只是我圈出來關鍵信息。先看下格式
frametype=0x17
=00010111
AVCPacketType =1
Composition Time=0x000043
后面就是NALU DATA
Audio
與視頻格式類似
前4位為音頻格式
值 | 類型 |
---|---|
0 | Linear PCM, platform endian |
1 | ADPCM |
2 | MP3 |
3 | Linear PCM, little endian |
4 | Nellymoser 16-kHz mono |
5 | Nellymoser 8-kHz mono |
6 | Nellymoser |
7 | G.711 A-law logarithmic PCM |
8 | G.711 mu-law logarithmic PCM |
9 | reserved |
10 | AAC |
11 | Speex |
14 | MP3 8-Khz |
15 | Device-specific sound |
接着2位為采樣率
值 | 類型 |
---|---|
0 | 5.5-kHz |
1 | 11-kHz |
2 | 22-kHz |
3 | 44-kHz |
對於AAC總是3
接着1位為采樣的長度
值 | 類型 |
---|---|
0 | snd8Bit |
1 | snd16Bit |
壓縮過的音頻都是16bit
接着1位為音頻類型
值 | 類型 |
---|---|
0 | sndMono |
1 | sndStereo |
對於AAC總是1
我們看到第三個TAG

ffmpeg播放器基本原理

其中,源文件模塊是這個播放器的起始,主要是為下面的各個模塊以數據包的方式提供數據流。具體而言就是從本地視頻文件中讀取出數據包,然后將其按照一定的順序排列,源源不斷得發送到下面的解復用模塊中。
解復用模塊根據源文件的容器格式來分離出視頻流、音頻流和字幕流,在加入時間同步等信息后傳送給下面的解碼模塊。為識別出不同的文件類型和媒體類型,常規的做法是讀取一部分數據,然后遍歷解復用播放器支持的文件格式和媒體數據格式,做匹配來確定是哪種文件類型,哪種媒體類型,有些媒體類型的原始數據外面還有其他的信息,比如時間,包大小,是否完整包等等。這里要注意的是,時鍾信息的計算工作也是在這個模塊完成,用於各媒體之間的同步。
解碼模塊作用就是解碼數據包,並且把同步時鍾信息傳遞下去。對視頻媒體而言,通常是解碼成YUV 數據,然后利用顯卡硬件直接支持YUV 格式數據Overlay 快速顯示的特性讓顯卡極速顯示。YUV格式是一個統稱,常見的有YV12,YUY2,UYVY 等等。有些非常古老的顯卡和嵌入式系統不支持YUV 數據顯示,那就要轉換成RGB 格式的數據,每一幀的每一個像素點都要轉換,分別計算RGB 分量,並且因為轉換是浮點運算,雖然有定點算法,還是要耗掉相當一部分CPU,總體上效率底下;對音頻媒體而言,通常是解碼成PCM 數據,然后送給聲卡直接輸出。
顏色空間轉換模塊的作用是把視頻解碼器解碼出來的數據轉換成當前顯示系統支持的顏色格式。通常視頻解碼器解碼出來的是YUV 數據,PC 系統是直接支持YUV 格式的,也支持RGB 格式,有些嵌入式系統只支持RGB 格式的。
渲染模塊對視頻來說就是顯示視頻圖像,對音頻來說就是播放聲音,對字幕來說就是顯示字幕,並保持視頻、音頻和字幕的同步播放。
ffmpeg轉碼基本原理
轉碼器在視音頻編解碼處理的程序中,屬於一個比較復雜的東西。因為它結合了視頻的解碼和編碼。一個視頻播放器,一般只包含解碼功能;一個視頻編碼工具,一般只包含編碼功能;而一個視頻轉碼器,則需要先對視頻進行解碼,然后再對視頻進行編碼,因而相當於解碼器和編碼器的結合。下圖例舉了一個視頻的轉碼流程。輸入視頻的封裝格式是FLV,視頻編碼標准是H.264,音頻編碼標准是AAC;輸出視頻的封裝格式是AVI,視頻編碼標准是MPEG2,音頻編碼標准是MP3。從流程中可以看出,首先從輸入視頻中分離出視頻碼流和音頻壓縮碼流,然后分別將視頻碼流和音頻碼流進行解碼,獲取到非壓縮的像素數據/音頻采樣數據,接着將非壓縮的像素數據/音頻采樣數據重新進行編碼,獲得重新編碼后的視頻碼流和音頻碼流,最后將視頻碼流和音頻碼流重新封裝成一個文件。
Linux下ndk編譯移植FFmpeg到Android平台:https://github.com/EricLi22/AndroidMultiMedia
FFmpeg 推流手機攝像頭,實現直播-:https://github.com/979451341/RtmpCamera
Android中使用ffmpeg編碼進行rtmp推流- https://www.jianshu.com/p/f3a55d3d1f5d
ffmpeg編碼RiemannLeeLiveProject- https://github.com/liweiping1314521/RiemannLeeLiveProject
Android利用ffmpeg推流-:https://github.com/WritingMinds/ffmpeg-android-java
https://blog.csdn.net/xiejiashu/article/details/74783875
https://blog.csdn.net/rainweic/article/details/94666527
https://blog.csdn.net/helloxiaoliang/article/details/81020482
https://my.oschina.net/u/1983790/blog/490524
https://blog.csdn.net/qq_26464039/article/details/84503335
https://www.cnblogs.com/zhangwc/p/9817642.html
https://blog.csdn.net/leixiaohua1020/article/details/12751349
https://www.jianshu.com/p/d541b317f71c
https://www.cnblogs.com/groundsong/p/5146112.html
https://www.cnblogs.com/lidabo/p/8662955.html
https://blog.csdn.net/jgw2008/article/details/84954902
https://www.cnblogs.com/lcxiao/p/11509132.html
ffprobe的常用命令:
https://www.jianshu.com/p/e14bc2551cfd
https://juejin.im/post/5a59993cf265da3e4f0a1e4b

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()