ffplay 播放yuv流文件有缓存吗

iOS视频边下边播--缓存播放数据流 - 简书
下载简书移动应用
写了24719字,被679人关注,获得了584个喜欢
iOS视频边下边播--缓存播放数据流
google搜索“iOS视频变下边播”,有好几篇博客写到了实现方法,其实只有一篇,其他都是copy的,不过他们都是使用的本地代理服务器的方式,原理很简单,但是缺点也很明显,需要自己写一个本地代理服务器或者使用第三方库httpSever。如果使用httpSever作为本地代理服务器,如果只缓存一个视频是没有问题的,如果缓存多个视频互相切换,本地代理服务器提供的数据很不稳定,crash概率非常大。
这里我采用ios7以后系统自带的方法实现视频边下边播,这里的边下边播不是单独开一个子线程去下载,而是把视频播放的数据给保存到本地。简而言之,就是使用一遍的流量,既播放了视频,也保存了视频。
用到的框架:&AVFoundation/AVFoundation.h&
用到的播放器:AVplayer
先说一下avplayer自身的播放原理,当我们给播放器设置好url等一些参数后,播放器就会向url所在的服务器发送请求(请求参数有两个值,一个是offset偏移量,另一个是length长度,其实就相当于NSRange一样),服务器就根据range参数给播放器返回数据。这就是大致的原理,当然实际的过程还是略微比较复杂。
下面进入主题
产品需求:
1.支持正常播放器的一切功能,包括暂停、播放和拖拽
2.如果视频加载完成且完整,将视频文件保存到本地cache,下一次播放本地cache中的视频,不再请求网络数据
3.如果视频没有加载完(半路关闭或者拖拽)就不用保存到本地cache&br/&
1.需要在视频播放器和服务器之间添加一层类似代理的机制,视频播放器不再直接访问服务器,而是访问代理对象,代理对象去访问服务器获得数据,之后返回给视频播放器,同时代理对象根据一定的策略缓存数据。
2.AVURLAsset中的resourceLoader可以实现这个机制,resourceLoader的delegate就是上述的代理对象。
3.视频播放器在开始播放之前首先检测是本地cache中是否有此视频,如果没有才通过代理获得数据,如果有,则直接播放本地cache中的视频即可。&br/&
视频播放器需要实现的功能
1.有开始暂停按钮
2.显示播放进度及总时长
3.可以通过拖拽从任意位置开始播放视频
4.视频加载中的过程和加载失败需要有相应的提示&br/&
代理对象需要实现的功能
1.接收视频播放器的请求,并根据请求的range向服务器请求本地没有获得的数据
2.缓存向服务器请求回的数据到本地
3.如果向服务器的请求出现错误,需要通知给视频播放器,以便视频播放器对用户进行提示
具体流程图
视频播放器处理流程
1.当开始播放视频时,通过视频url判断本地cache中是否已经缓存当前视频,如果有,则直接播放本地cache中视频
2.如果本地cache中没有视频,则视频播放器向代理请求数据
3.加载视频时展示正在加载的提示(菊花转)
4.如果可以正常播放视频,则去掉加载提示,播放视频,如果加载失败,去掉加载提示并显示失败提示
5.在播放过程中如果由于网络过慢或拖拽原因导致没有播放数据时,要展示加载提示,跳转到第4步
代理对象处理流程
1.当视频播放器向代理请求dataRequest时,判断代理是否已经向服务器发起了请求,如果没有,则发起下载整个视频文件的请求
2.如果代理已经和服务器建立链接,则判断当前的dataRequest请求的offset是否大于当前已经缓存的文件的offset,如果大于则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向后拖拽,并且超过了已缓存的数据时才会出现)
3.如果当前的dataRequest请求的offset小于已经缓存的文件的offset,同时大于代理向服务器请求的range的offset,说明有一部分已经缓存的数据可以传给播放器,则将这部分数据返回给播放器(此时应该是由于播放器向前拖拽,请求的数据已经缓存过才会出现)
4.如果当前的dataRequest请求的offset小于代理向服务器请求的range的offset,则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向前拖拽,并且超过了已缓存的数据时才会出现)
5.只要代理重新向服务器发起请求,就会导致缓存的数据不连续,则加载结束后不用将缓存的数据放入本地cache
6.如果代理和服务器的链接超时,重试一次,如果还是错误则通知播放器网络错误
7.如果服务器返回其他错误,则代理通知播放器网络错误
resourceLoader的难点处理
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
[self.pendingRequests addObject:loadingRequest];
[self dealWithLoadingRequest:loadingRequest];
return YES;
播放器发出的数据请求从这里开始,我们保存从这里发出的所有请求存放到数组,自己来处理这些请求,当一个请求完成后,对请求发出finishLoading消息,并从数组中移除。正常状态下,当播放器发出下一个请求的时候,会把上一个请求给finish。
下面这个方法发出的请求说明播放器自己关闭了这个请求,我们不需要再对这个请求进行处理,系统每次结束一个旧的请求,便必然会发出一个或多个新的请求,除了播放器已经获得整个视频完整的数据,这时候就不会再发起请求。
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
[self.pendingRequests removeObject:loadingRequest];
下面这个方法是对播放器发出的请求进行填充数据
- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest
long long startOffset = dataRequest.requestedO
if (dataRequest.currentOffset != 0) {
startOffset = dataRequest.currentO
if ((self.task.offset +self.task.downLoadingOffset) & startOffset)
//NSLog(@"NO DATA FOR REQUEST");
return NO;
if (startOffset & self.task.offset) {
return NO;
NSData *filedata = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:_videoPath] options:NSDataReadingMappedIfSafe error:nil];
// This is the total data we have from startOffset to whatever has been downloaded so far
NSUInteger unreadBytes = self.task.downLoadingOffset - ((NSInteger)startOffset - self.task.offset);
// Respond with whatever is available if we can't satisfy the request fully yet
NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes);
[dataRequest respondWithData:[filedata subdataWithRange:NSMakeRange((NSUInteger)startOffset- self.task.offset, (NSUInteger)numberOfBytesToRespondWith)]];
long long endOffset = startOffset + dataRequest.requestedL
BOOL didRespondFully = (self.task.offset + self.task.downLoadingOffset) &= endO
return didRespondF
这是对存放所有的请求的数组进行处理
- (void)processPendingRequests
NSMutableArray *requestsCompleted = [NSMutableArray array];
//请求完成的数组
//每次下载一块数据都是一次请求,把这些请求放到数组,遍历数组
for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests)
[self fillInContentInformation:loadingRequest.contentInformationRequest]; //对每次请求加上长度,文件类型等信息
BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; //判断此次请求的数据是否处理完全
if (didRespondCompletely) {
[requestsCompleted addObject:loadingRequest];
//如果完整,把此次请求放进 请求完成的数组
[loadingRequest finishLoading];
[self.pendingRequests removeObjectsInArray:requestsCompleted];
//在所有请求的数组中移除已经完成的
resourceLoader的难点基本上就是上面这点了,说到播放器,下面便顺便讲下AVPlayer的难点。
难点:对播放器状态的捕获
举个简单的例子,视频总长度60分,现在缓冲的数据才10分钟,然后拖动到20分钟的位置进行播放,在网速较慢的时候,视频从当前位置开始播放,必然会出现一段时间的卡顿,为了有一个更好的用户体验,在卡顿的时候,我们需要加一个菊花转的状态,现在问题就来了。
在拖动到未缓冲区域内,是否需要加菊花转,如果加,要显示多久再消失,而且如果在网速很慢的时候,播放器如果等了太久,哪怕最后有数据了,播放器也已经“死”了,它自己无法恢复播放,这个时候需要我们人为的去恢复播放,如果恢复播放不成功,那么过一段时间需要再次恢复播放,是否恢复播放成功,这里也需要捕获其状态。所以,如果要有一个好的用户体验,我们需要时时知道播放器的状态。
有两个状态需要捕获,一个是正在缓冲,一个是正在播放,监听播放的“playbackBufferEmpty”属性就可以捕获正在缓冲状态,播放器的时间监听器则可以捕获正在播放状态,我的demo中一共有4个状态:
typedef NS_ENUM(NSInteger, TBPlayerState) {
TBPlayerStateBuffering = 1,
TBPlayerStatePlaying
TBPlayerStateStopped
TBPlayerStatePause
这样可以对播放器更好的把握和处理了。然后说一说在缓冲时候的处理,以及缓冲后多久去播放,处理方法:进入缓冲状态后,缓冲2秒后去手动播放,如果播放不成功(缓冲的数据太少,还不足以播放),那就再缓冲2秒再次播放,如此循环,看详细代码:
- (void)bufferingSomeSecond
// playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略
static BOOL isBuffering = NO;
if (isBuffering) {
isBuffering = YES;
// 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
[self.player pause];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 如果此时用户已经暂停了,则不再需要开启播放了
if (self.isPauseByUser) {
isBuffering = NO;
[self.player play];
// 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间
isBuffering = NO;
if (!self.currentPlayerItem.isPlaybackLikelyToKeepUp) {
[self bufferingSomeSecond];
这个demo花了我很长的时间,实现这个demo我也遇到了很多坑最后才完成的,现在我奉献出来,也许对你会有所帮助。如果你觉得不错,还请为我Star一个,也算是对我的支持和鼓励。
多谢打赏!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
玩转简书的第一步,从这个专题开始。
想上首页热门榜么?好内容想被更多人看到么?来投稿吧!如果被拒也不要灰心哦~入选文章会进一个队...
· 130541人关注
专题内容主要包括OC、swift等涉及到iOS开发进阶的内容。
swift可以关注下我的另一个专题:
swift开发...
· 20086人关注
· 6712人关注
多谢打赏!
选择支付方式:static uint64_t global_video_pkt_pts= AV_NOPTS_VALUE;
还重写了分配帧和销毁帧的方法:C/C++ code
static int my_get_buffer(struct AVCodecContext *c, AVFrame *pic){
int ret= avcodec_default_get_buffer(c, pic);
uint64_t *pts= av_malloc(sizeof(uint64_t));
*pts= global_video_pkt_
pic-&opaque=
static void my_release_buffer(struct AVCodecContext *c, AVFrame *pic){
if(pic) av_freep(&pic-&opaque);
avcodec_default_release_buffer(c, pic);
在使用这个全局时间戳的时候,是这样使用的:C/C++ code
/* NOTE: ipts is the PTS of the _first_ picture beginning in
this packet, if any */
global_video_pkt_pts= pkt-&
len1 = avcodec_decode_video(is-&video_st-&codec,
frame, &got_picture,
pkt-&data, pkt-&size);
(decoder_reorder_pts || pkt-&dts == AV_NOPTS_VALUE)
&& frame-&opaque && *(uint64_t*)frame-&opaque != AV_NOPTS_VALUE)
pts= *(uint64_t*)frame-&
else if(pkt-&dts != AV_NOPTS_VALUE)
pts= pkt-&
pts *= av_q2d(is-&video_st-&time_base);
if (len1 & 0)
if (got_picture) {
if (output_picture2(is, frame, pts) & 0)
av_free_packet(pkt);
if (cur_stream)
stream_pause(cur_stream);
请问,这里为什么要使用一个全局变量,而不是用临时变量呢?而且,一般是不到万不得已,是不是用全局变量的啊。------解决方案--------------------查看代码,如果不需要暂存信息,就可以不用。它这里用,应该是有道理的。
------解决方案--------------------
之所以用全局变量,是因为在my_get_buffer里要用到
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 12345678910 Copyright & &&版权所有1 我一直使用flex4和flash开发视频聊天的程序,以前再flex中和flash直接写上服务器路径然后指定play的流的名称就可以了。代码如下:
// Check for reconnect.
if ( nsPlay != null )
    // Stop and close previous NetStream.
    var stopStreamEvent : StopStreamEvent = new StopStreamEvent();
    stopStreamEvent.dispatch();
// Setup NetStream for playback.
nsPlay = new NetStream( main.media.nc );
nsPlay.bufferTime = bufferT
nsPlay.receiveAudio( audio );
nsPlay.receiveVideo( video );
nsPlay.client =
main.media.videoRemote = new Video( main.cameraSettings.width, main.cameraSettings.height );
main.media.videoRemote.attachNetStream( nsPlay );
main.playbackState =
nsPlay.play( streamName );
上面的stream就是stream0 你会看到我再接下来的c++使用ffplay去播放也是这样写的。
2 使用ffplay播放直播流:注意再这个后面多加了一个参数live=1,而且必须要加该参数,否则再red5上你能点播但是不能直播
D:\Tools\ffmpeg&ffplay -i &rtmp://localhost/oflaDemo/stream0 live=1&
3 说明之前看到一个博客中提到需要修改red5源码,我修改之后编译还是不可以,而且他代码就有问题
start_time是long数据,怎么和-2比较的,这样语法就会报错。不多说这个了。
4 实例演示
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:121150次
积分:1456
积分:1456
排名:千里之外
原创:35篇
评论:45条
北国风光,千里冰封,
万里雪飘。望长城内外,
惟余莽莽;大河上下,
顿失滔滔。山舞银蛇,
原驰蜡象,欲与天公试比高。
须晴日,看红装素裹,
分外妖娆。江山如此多娇,
引无数英雄竞折腰。
惜秦皇汉武,略输文采;
唐宗宋祖,稍逊风骚。
一代天骄,成吉思汗,
只识弯弓射大雕。
数风流人物,还看今朝。
(1)(1)(1)(2)(1)(1)(3)(1)(2)(4)(3)(1)(1)(2)(2)(1)(1)(1)(3)(3)(3)(1)(1)(1)博客访问: 4019426
博文数量: 676
博客积分: 2150
博客等级: 上尉
技术积分: 11783
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: 网络与安全
FFMpeg处理RTMP流有两种方式:
& 一个是使用自带的RTMP代码功能;
& 一个是使用第三方库librtmp;
下面就这两种方式的一些使用和差异做了个总结;
一、自带RTMP代码功能
FFmpeg自带的RTMP代码只支持RTMP协议,不支持rtmpt,rtmpe,rtmpte和rtmps协议;
命令行设置如下:
1. 将RTMP流原样保存成文件
# ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec copy -vcodec copy -f flv -y test.flv
2. 将RTMP流转码保存成文件
# ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec ... -vcodec ... -f mp4 -y test.mp4
3. 将RTMP流转码后再以RTMP流的方式推送到RTMP流服务器
# ./ffmpeg -i rtmp://192.168.1.11:1935/live/teststream -acodec ... -vcodec ... -f flv rtmp://10.2.11.111/live/newstream
FFMpeg自带RTMP代码只支持RTMP流格式如:
rtmp://server:port/app/stream_name (eg: rtmp://192.168.1.11:80/live/test)
不支持RTMP流格式如:
rtmp://192.168.1.11:80/live/app/test
要想支持这种格式的RTMP流,就需要更专业和强大的每三方库&
二、第三方库librtmp
如何让FFMpeg链接该库可以参见文章:
http://blog.csdn.net/fireroll/article/details/8607955
这样FFMpeg就可以支持rtmp://, rtmpt://, rtmpe://, rtmpte://,以及 rtmps://协议了。
链接了librtmp的FFMpeg接受一个字符串的输入方式,
如:"rtmp://server:port/app/playpath/stream_name live=1 playpath=xxx ..."
NOTE:引号是必须的;
1. 保存RTMP直播流原样保存成文件:
# ./ffmpeg -i "rtmp:///live/newcetv1 live=1" -vcodec copy -acodec copy -y cetv1.flv &&
2. 将RTMP流转码后再以RTMP流的方式推送到RTMP流服务器
# ./ffmpeg -i "rtmp://192.168.1.11:1935/live/app/teststream live=1" -acodec ... -vcodec ... -f flv rtmp://10.2.11.111/live/newstream
3. 用ffplay播放RTMP直播流:
ffplay "rtmp:///live/newcetv1 live=1"&
4. 在使用FFMPEG类库进行编程的时候,也是一样的,
只需要将字符串传递给avformat_open_input()就行了,形如:
ffplay "rtmp:///live/newcetv1 live=1" &
char url[]="rtmp://live.hkstv./live/hks live=1"; &
avformat_open_input(&pFormatCtx,url,NULL,&avdic) &
三、librtmp支持的参数:
http://rtmpdump.mplayerhq.hu/librtmp.3.html
librtmp RTMPDump Real-Time Messaging Protocol API
2. LIBRARY
RTMPDump RTMP (librtmp, -lrtmp)
3. SYNOPSIS
4. DESCRIPTION
The Real-Time Messaging Protocol (RTMP) is used for streaming multimedia content across a TCP/IP network.&
This API provides most client functions and a few server functions needed to support RTMP, RTMP tunneled in HTTP (RTMPT),&
encrypted RTMP (RTMPE), RTMP over SSL/TLS (RTMPS) and tunneled variants of these encrypted types (RTMPTE, RTMPTS).&
The basic RTMP specification has been published by Adobe but this API was reverse-engineered without use of the Adobe specification.&
As such, it may deviate from any published specifications but it usually duplicates the actual behavior of the original Adobe clients.
The RTMPDump software package includes a basic client utility program in rtmpdump(1), some sample servers,&
and a library used to provide programmatic access to the RTMP protocol. This man page gives an overview of the RTMP library routines.&
These routines are found in the -lrtmp library. Many other routines are also available, but they are not documented yet.
The basic interaction is as follows. A session handle is created using RTMP_Alloc() and initialized using RTMP_Init().&
All session parameters are provided using RTMP_SetupURL(). The network connection is established using RTMP_Connect(),&
and then the RTMP session is established using RTMP_ConnectStream(). The stream is read using RTMP_Read().&
A client can publish a stream by calling RTMP_EnableWrite() before the RTMP_Connect() call,&
and then using RTMP_Write() after the session is established. While a stream is playing it may be paused and unpaused&
using RTMP_Pause(). The stream playback position can be moved using RTMP_Seek(). When RTMP_Read() returns 0 bytes,&
the stream is complete and may be closed using RTMP_Close(). The session handle is freed using RTMP_Free().
All data is transferred using FLV format. The basic session requires an RTMP URL. The RTMP URL format is of the form
& rtmp[t][e|s]://hostname[:port][/app[/playpath]]
Plain rtmp, as well as tunneled and encrypted sessions are supported.
Additional options may be specified by appending space-separated key=value pairs to the URL.&
Special characters in values may need to be escaped to prevent misinterpretation by the option parser.&
The escape encoding uses a backslash followed by two hexadecimal digits representing the ASCII value of the character.&
E.g., spaces must be escaped as \20 and backslashes must be escaped as \5c.
5. OPTIONS
5.1 Network Parameters
These options define how to connect to the media server.
& socks=host:port
Use the specified SOCKS4 proxy.
5.2 Connection Parameters
These options define the content of the RTMP Connect request packet.&
If correct values are not provided, the media server will reject the connection attempt.
& app=name
& & & Name of application to connect to on the RTMP server. Overrides the app in the RTMP URL.&
& & & Sometimes the librtmp URL parser cannot determine the app name automatically,&
& & & so it must be given explicitly using this option.
& tcUrl=url
& & & URL of the target stream. Defaults to rtmp[t][e|s]://host[:port]/app.
& pageUrl=url
& & & URL of the web page in which the media was embedded. By default no value will be sent.
& swfUrl=url
& & & URL of the SWF player for the media. By default no value will be sent.
& flashVer=version
& & & Version of the Flash plugin used to run the SWF player. The default is "LNX 10,0,32,18".
& conn=type:data
& & & Append arbitrary AMF data to the Connect message.&
& & & The type must be B for Boolean, N for number, S for string, O for object, or Z for null.&
& & & For Booleans the data must be either 0 or 1 for FALSE or TRUE, respectively.&
& & & Likewise for Objects the data must be 0 or 1 to end or begin an object, respectively.&
& & & Data items in subobjects may be named, by prefixing the type with 'N' and specifying the name before the value,&
& & & e.g. NB:myFlag:1. This option may be used multiple times to construct arbitrary AMF sequences. E.g.
& & & & conn=B:1 conn=S:authMe conn=O:1 conn=NN:code:1.23 conn=NS:flag:ok conn=O:0
5.3 Session Parameters
These options take effect after the Connect request has succeeded.
& playpath=path
& & &Overrides the playpath parsed from the RTMP URL.&
& & &Sometimes the rtmpdump URL parser cannot determine the correct playpath automatically,&
& & &so it must be given explicitly using this option.
& playlist=0|1
& & If the value is 1 or TRUE, issue a set_playlist command before sending the play command.&
& & The playlist will just contain the current playpath. If the value is 0 or FALSE, the set_playlist command will not be sent.&
& & The default is FALSE.
& live=0|1
& & Specify that the media is a live stream. No resuming or seeking in live streams is possible.
& subscribe=path
& & Name of live stream to subscribe to. Defaults to playpath.
& start=num
& & Start at num seconds into the stream. Not valid for live streams.
& stop=num
& & Stop at num seconds into the stream.
& buffer=num
& & Set buffer time to num milliseconds. The default is 30000.
& timeout=num
& & Timeout the session after num seconds without receiving any data from the server. The default is 120.
5.4 Security Parameters
These options handle additional authentication requests from the server.
& token=key
& & Key for SecureToken response, used if the server requires SecureToken authentication.
& jtv=JSON
& & JSON token used by legacy Justin.tv servers. Invokes NetStream.Authenticate.UsherToken
& swfVfy=0|1
& & If the value is 1 or TRUE, the SWF player is retrieved from the specified swfUrl for performing SWF Verification.&
& & The SWF hash and size (used in the verification step) are computed automatically.&
& & Also the SWF information is cached in a .swfinfo file in the user's home directory,&
& & so that it doesn't need to be retrieved and recalculated every time. The .swfinfo file records the SWF URL,&
& & the time it was fetched, the modification timestamp of the SWF file, its size, and its hash.&
& & By default, the cached info will be used for 30 days before re-checking.
& swfAge=days
& & Specify how many days to use the cached SWF info before re-checking.&
& & Use 0 to always check the SWF URL. Note that if the check shows that the SWF file has the same modification timestamp as before,&
& & it will not be retrieved again.
5.5 EXAMPLES
An example character string suitable for use with RTMP_SetupURL():
& "rtmp://flashserver:1935/ondemand/thefile swfUrl=http://flashserver/player.swf swfVfy=1"
阅读(23864) | 评论(1) | 转发(2) |
相关热门文章
给主人留下些什么吧!~~
博主你好,有两个问题请教一下:1.将RTSP流(IP&Camera)通过FFMpeg转发至RTMP流服务器(Adobe&Flash&Media&Server),RTMP流格式:rtmp://localhost/live/livestream,请问一下,流服务器同时也以RTMP协议对外直播,客户端连接到流服务器的地址也是rtmp://localhost/live/livestream,我看到流服务器提供RTMP服务的端口是1935,难道一个端口又要承担接受的流媒体,又要对外发送流媒体吗,这样不合理把。2.RTMP流格式问题,rtmp://192.168.1.11:80/live/test&和&rtmp://192.168.1.11:80/live/app/test&这两种格式在应用上有什么区别吗?
请登录后评论。

我要回帖

更多关于 ffplay 播放yuv 的文章

 

随机推荐