在上篇文章《Jrtplib收发H264文件 + FFMPEG解码+VFW播放视频》 里,我们采用的模式是发送端读取本地H264文件, 把完整的Naul(包含起始码) 逐个发送给接收端,接收端收到后,对单个Naul数据进行解码,然后播放出来。

这其中关键的地方在于,把包含起始码的Naul数据分配给AVPacket对象,然后就可以解码了,如下

AVPacket packet; 
        av_new_packet(&packet,len); 
        memcpy(packet.data, Buf, len);

其实FFMPEG解码还有另一种模式,通过回调函数,从指定的地方(比如本地文件,D:\testl.flv, 或者存放在内存中的视频数据)读取数据,然后解码。

通过回调函数读本地文件的意义不大,本文主要关注通过回调函数从内存从抓数据的场景.

经测试,ffmpeg应该是先通过多次调用回调函数从内存中预加载一部分数据进行处理,处理完后,再预加载第二批数据,以此类推.

我们先来看发送端,发送端比上一篇的版本更简单,只需打开本地磁盘文件,每次读入1024字节的数据(当然具体大小可自己拟定),然后把读入的数据发送出去即可,不用关心读入的1024字节数据是不是完整的Naul包,读数据的伪代码如下:

FILE *fp_open;
    uint8_t buff[1024*5] = {0};
    int  bufsize = 1024; //每次读1024字节,谁便取,不超过1400就行
 
    fp_open = fopen("cuc_ieschool.flv","rb");
    while( !feof(fp_open) )
    {  
        int true_size = fread(buff,1,bufsize,fp_open);  //读一次,内部位移
        printf("\n--> read file %d bytes",true_size);
        sender.SendH264Nalu(&sess, buff,true_size);     // 每次发送固定大小的数据
        RTPTime::Wait(0.005); //间隔5毫秒
    }

发送数据的伪代码如下:
       if(buflen <= MAXLEN)  //每次读的是1024大小的数据
    {   
        sess->SetDefaultMark(true);  
        status = sess->SendPacket((void *)&pSendbuf[0],buflen);  
        checkerror(status);
        printf("send_packt 0 len = %d\n",buflen);  
    }

发送端就这么简单,我们再看接收端,接收端收到的是一个1024大小的buffer,为了存储这些buffer,我定义了如下的结构体

// 处理收到的1024大小的数据包
typedef struct
{
    unsigned length;        //包大小,除非是最后一个包,不然是1024
    uint8_t *buf;           // new uint8_t[length]
}PacketNode_t;

以及存储buffer的list容器和同步锁,如下

CCriticalSection    m_cs; //同步锁

list<PacketNode_t>  m_packetList; //包列表

首先,在对话框初始化的地方,做相应的准备工作,代码如下:

m_DrawDib = DrawDibOpen();  // FVW用
       m_timeCount = 0;       //定时器延迟触发用
       m_bInitFlag = FALSE;   //ffmpeg初始化标志位 
 
    //网络初始化
    WSADATA dat;
    WSAStartup(MAKEWORD(2,2),&dat);
 
    sessparams.SetOwnTimestampUnit(1.0/10.0);         
    transparams.SetPortbase(12346);   //监听端口
    int status = sess.Create(sessparams,&transparams);    
    checkerror(status);  
 
    SetTimer(666,5,NULL);      // 定时器,收rtp包
    SetTimer(888,40,NULL);     // 定时器,解码和显示视频

其中,定时器666的作用是收rtp, 其实就是一个个1024大小的包,处理逻辑如下:

if(666 == nIDEvent)
    {
        RTPPacket *pack;  
        int    status = sess.Poll();  // 主动收包
        checkerror(status);  
        sess.BeginDataAccess();  
        if (sess.GotoFirstSourceWithData())  
        {  
            do  
            {  
                while ((pack = sess.GetNextPacket()) != NULL)  
                {  
                    uint8_t * loaddata = pack->GetPayloadData();  
                    size_t len         = pack->GetPayloadLength();  
                   // TRACE(" Get packet-> %d  \n ",pack->GetPayloadLength());  
 
                    // 构建包对象
                    PacketNode_t  temNode;
                    temNode.length = len;
                    temNode.buf = new uint8_t[len];
                    memcpy(temNode.buf,loaddata,len);     
 
                    m_cs.Lock();
                    m_packetList.push_back(temNode); //存包列表
                    m_cs.Unlock();
 
                    sess.DeletePacket(pack);  
                }  
            } while (sess.GotoNextSourceWithData());  
        } 
        sess.EndDataAccess();  
    }

大概逻辑是把收到的1024包,用结构体PacketNode_t保存起来,放入列表,

定时器888的作用是,初始化ffmpeg(当然只执行一次),解码视频,然后播放,代码如下:

if(888 == nIDEvent)
    {
        m_timeCount++;
        if(m_timeCount <200) // 每次40毫秒,8秒后才触发定时器,让list存储足够多的数据
            return;
 
        if( FALSE == m_bInitFlag )
        {
            if( this->InitDecode()<0  ) //ffmpeg初始化
            {
                PostQuitMessage(0);
            }
        }
        else
        {
            this->DecodeVideoFrame(); //开始解压播放
        }
    }

其中要8秒钟后才执行ffmpeg初始化,因为我们要保证先收到一部分视频数据,初始化的时候,在注册回调函数的时候,ffmpeg会预读一部分数据,方便后面解析数据,得到视频的解码器等信息。

ffmpeg初始化函数为InitDecode(),具体可参见源码,其中注册回调函数的代码为

AVIOContext *avio =avio_alloc_context(iobuffer, 1024*MAX_PACKET_COUNT,0,this,ReadNetPacket,NULL,NULL); 
回调函数ReadNetPacket内部调用的是对话框的ToReadNetPacket()函数,如下,

int CShowH264_PictureDlg::ToReadNetPacket(uint8_t *buf, int buf_size)
{
    int nIndex = 0; //计数器
    int nsize = 0;
    m_cs.Lock();
    if(!m_packetList.empty())
    {
        list<PacketNode_t>::iterator itr;
        for(itr = m_packetList.begin();itr != m_packetList.end();) // 顺序遍历
        {
            nIndex++; 
            if( nIndex > MAX_PACKET_COUNT) 
            {
               break; //第31个就跳出循环
            }
            //PacketNode_t = *itr;
            memcpy(buf+nsize, itr->buf, itr->length);
            nsize += itr->length;
 
            delete[] itr->buf; //释放内存
            m_packetList.erase(itr++);   //list删除item
        }
    }
    else
    {
        nsize = -1;  //表示没有数据可读
    }
    m_cs.Unlock();
 
    TRACE("\n  callback read --> %d ", nsize);
    return nsize;
}

这个函数就是 ffmpeg内部要预加载数据时,通过回调,从网络包容器里获取数据的方法.

然后,每隔40毫秒,执行一次DecodeVideoFrame()函数,解码视频,如下:

int CShowH264_PictureDlg::DecodeVideoFrame()
{
    int ret, got_picture;
    if(av_read_frame(pFormatCtx, packet) >= 0)
    {
        if(packet->stream_index==videoindex)
        {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
            if(ret < 0)
            {
                TRACE("Decode Error.(解码错误)\n");
                return -1;
            }
            else if(got_picture)
            {
                pFrame->data[0] += pFrame->linesize[0]*(pCodecCtx->height-1);
                pFrame->linesize[0] *= -1;                      
                pFrame->data[1] += pFrame->linesize[1]*(pCodecCtx->height/2-1);
                pFrame->linesize[1] *= -1;
                pFrame->data[2] += pFrame->linesize[2]*(pCodecCtx->height/2-1);
                pFrame->linesize[2] *= -1;
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
                this->display_pic(pFrameYUV->data[0], pCodecCtx->width, pCodecCtx->height); //显示视频
                //TRACE("\n ---> 准备播放了 ");
            }
        }
        av_free_packet(packet);
    }
    return 0;
}

显示视频的方法和上一篇文章的代码是一样,不再赘述.
本demo和上一篇文件比起来,视频格式不限于h264文件,常见的mp4,mkv,rmvb等格式都支持,经测试,如果播放常规的电影,回调方式会比较卡(哪怕回调播放本地视频,非网络传输方式),如果视频比较小,画面不大,效果还行.

本文只是演示了网络发包的情况下, 采用回调播放的一种思路而已.

本文源码下载: http://download.csdn.net/detail/heker2010/9910101

我修改的雷神的本地回调播放的demo:

http://download.csdn.net/detail/heker2010/9910081

在此之前,我写的回调播放h264的demo,本来想删除的,发现有人下载过了,就保留吧

http://download.csdn.net/detail/heker2010/9908914

参考文章:

ffmpeg 从内存中读取数据(或将数据输出到内存)

http://blog.csdn.net/leixiaohua1020/article/details/12980423

FFMPEG实时解码网络视频流(回调方式)

http://blog.csdn.net/fang437385323/article/details/71247065
--------------------- 
作者:谁便取名好难 
来源:CSDN 
原文:https://blog.csdn.net/heker2010/article/details/76084973 
版权声明:本文为博主原创文章,转载请附上博文链接!

Jrtplib发送视频文件 + FFMPEG解码+VFW播放视频 (回调方式)相关推荐

  1. VC++实现视频聊天:FFmpeg解码+SDL播放视频

    经过网络传输接收到的码流,已经存放在公共链表 PacketNode_t 中,码流经过解码成YUV或RGB后才能播放,接下来就介绍FFmpeg解码过程和 SDL 播放视频. FFmpeg 解码 码流解码 ...

  2. android全平台基于ffmpeg解码本地MP4视频推流到RTMP服务器

    音视频实践学习 android全平台编译ffmpeg以及x264与fdk-aac实践 ubuntu下使用nginx和nginx-rtmp-module配置直播推流服务器 android全平台编译ffm ...

  3. 【FFmpeg】ffplay 播放视频命令 ( 播放 | 暂停 | 停止 | 音量控制 | 进度控制 | 音频流 / 视频流 / 字幕流 / 节目切换 )

    FFmpeg 系列文章目录 [FFmpeg]Windows 搭建 FFmpeg 命令行运行环境 [FFmpeg]FFmpeg 相关术语简介 ( 容器 | 媒体流 | 数据帧 | 数据包 | 编解码器 ...

  4. linux下使用libmad库实现mp3文件的解码、播放

    linux下使用libmad库实现mp3文件的解码.播放 目录(?)[+] 准备工作 解码流程 播放 遇到的问题 据说这个更新到2004年2月的libmad是一种高品质的MPEG音频解码器,支持24- ...

  5. cocos android 播放视频,Cocos2d-x IOS 和Android播放视频

    本篇文章会给大家介绍在对IOS 和Android开发时,是如何实现播放视频这个功能的,下面就分别给大家介绍下. 一. iOS播放本地视频 对于ios平台的视频播放,这里直接使用MediaPlayer. ...

  6. 腾讯视频如何多倍速播放视频

    1.首先打开手机桌面上的"腾讯视频". 怎么下载腾讯视频里的视频_腾讯视频如何多倍速播放视频 2.再进入了腾讯视频的首页后,在首页的页面上选择一个你想要多倍速播放的视频,点击进入这 ...

  7. 腾讯视频真实下载地址_腾讯视频如何多倍速播放视频

    腾讯视频官网版是一款专注视频播放的客户端软件,您可运行腾讯视频,在线享受奇艺网站内全部免费高清正版视频.腾讯视频视频播放器内容丰富,影视更新快,包含腾讯视频所有的视频内容,在线享受腾讯视频站内全部免费 ...

  8. Android 基于GSYVideoPlayer实现短视频软件上下滑自动播放视频

    先放效果图 两个视频的地址: private final String mp4_a = "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212 ...

  9. python读视频文件_python读取和保存视频文件

    如何用python实现视频关键帧提取并保存为图片?也许你会觉得对小编多做一点事你会觉得你很爽,可是在小编看来这是不屑的 import cv2vc = cv2.VideoCapture('Test.av ...

最新文章

  1. sskeychain使用(轻量级框架)
  2. 标记三维点_三维扫描仪对汽车钣金外形检测折弯件钣金件热成型加工件的应用...
  3. 为你写诗:3 步搭建 Serverless AI 应用
  4. BAPI_BILLINGDOC_CANCEL1解析
  5. 世纪联华的 Serverless 之路
  6. CCP(Cost complexity pruning) on sklearn with python implemention
  7. 1.3编程基础之算术表达式与顺序执行 06 甲流疫情死亡率
  8. Web笔记-移动前端开发笔记
  9. 5.6亿人零存款,人均贷款过4万元,这才是年轻人真实的生存现状?
  10. WPF程序,运行时,结束时,要运行的操作(自动保存,检查单程序)
  11. 受移动竞争所致,联通的宽带用户大跌,电信将面临同样遭遇
  12. zeros什么意思_zeros:zeros还是zeroes?4)是什么意思?
  13. 从1万到100万的理财法则
  14. AcWing2279 网络战争 (01分数规划+网络流 最小割模型)
  15. Google Earth Engine——MERRA-2 M2T1NXAER:1980-2022年气溶胶逐日数据集
  16. openCV读入图片,openGL实现纹理贴图
  17. iEx.ec演示DApp的初步了解,快来“尝鲜”iEx.ec分布式云的强大算力
  18. 最新iApp源码小易工具箱源码+功能超级多
  19. ERP原理 : 第六节 采购管理的工作原理
  20. b2b2c系统jwt权限源码分享part1

热门文章

  1. 制作网站首页(小兔鲜儿电商购物平台)
  2. Expected tensor for argument #1 'input' to have the same device as tensor for argument #2 'weight';
  3. 计算机类型变废为宝创意设计,旧玩具还能这么玩!10个DIY创意教你如何变废为宝...
  4. node连接MySQL的三种方式
  5. 暗黑血统2android,暗黑血统2安卓版下载_暗黑血统2游戏下载-d7ol休闲益智app
  6. 余辉和眼图_眼图 - 国搜百科
  7. 【mfxp系统】xp与Windows 98电脑互访问题如何解决
  8. copy pods resources
  9. 【MySQL】MySQL 数据库设计铁律
  10. 加工中心逆变器散热风扇故障_为什么模具加工中心得主轴会出现故障