【转】V4L2+swscale+X264+live555实现流媒体服务端
写这边博客,一方面是因为自己在做项目的时候不太做笔记,怕以后自己忘记了。另一方面,是让正在寻求资料的同行少走一点弯路吧。不能说我这个方案怎么的好,至少是有一点参考价值的。这边博客需要一定基础才能看明白的,当然对V4L2,Swscale,x264,live555不太了解的人,我这里会给出我当时看的资料链接,但愿链接一直有效。也感谢了那些写博客的作者。
下面贴出一些资料的链接吧,认真看完肯定收获不少。
一篇关于V4L2(Video For Linux Two)http://www.cnblogs.com/lixiaoming90/archive/2012/08/25/2657019.html写的很不错的文章,认真看完了,我觉得V4L2视屏捕捉肯定不是问题。
Swscale是ffmpeg库的一部分,主要是做图像格式的转换和拉伸,缩放。这边文章介绍了Swscale的使用,http://blog.csdn.net/leixiaohua1020/article/details/14215391 。
x264是做H264编码用的,要注意的是x264的输入图像格式是:I410也就是420P。这样可以用Swscale对原始图像进行转格式。http://www.cnblogs.com/fojian/archive/2012/09/01/2666627.html 。
live555 是一个流媒体框架,主要是做RTSP协议的。写的很不错(大家都这么说的),但是读live555源代码还是有一定难度的。这里我就不过多的介绍live555的实现机制了。但是资料还是给出了,感谢作者。http://blog.csdn.net/niu_gao/article/category/1066093 。
下面我开始讲解整个服务器的实现过程吧,
/*
* V4L2.cpp
*
* Created on: 2013年12月17日
* Author: ny
*/ #include "V4L2.h" V4L2::V4L2()
{
fd = -;
buffers = NULL;
width = ;
height = ;
CLEAR(fmt); //设置帧格式
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
} V4L2::~V4L2()
{
close(fd);
} int V4L2::getWidth()
{
return width;
} int V4L2::getHeight()
{
return height;
} bool V4L2::setSize(int width, int height)
{
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -)
{
printf("Can not VIDIOC_S_FMT\n");
return false;
}
getSizeInfo();
return true;
} void V4L2::getSizeInfo()
{
if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -)
{
printf("Can not VIDIOC_G_FMT\n");
return;
}
this->width = fmt.fmt.pix.width;
this->height = fmt.fmt.pix.height;
} bool V4L2::initDev(const char * devName, int width, int height)
{
v4l2_capability cap; fd = open(devName, O_RDWR, ); //打开设备
if (fd == -)
{
printf("Can not open %s\n", devName);
return false;
}
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -) //查询设备的功能
{
printf("Can not get Capability\n");
return false;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
printf("Can not capture video\n");
return false;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING))
{
printf("does not support streaming\n");
}
if (!setSize(width, height))
return false;
printf("fmt.fmt.pix.bytesperline:%d\n", fmt.fmt.pix.bytesperline);
printf("format:%c%c%c%c\n", (fmt.fmt.pix.pixelformat & 0xff),
((fmt.fmt.pix.pixelformat >> ) & 0xff),
((fmt.fmt.pix.pixelformat >> ) & 0xff),
((fmt.fmt.pix.pixelformat >> ) & 0xff)); return initMmap();
}
bool V4L2::initMmap()
{
struct v4l2_requestbuffers req;
unsigned int n_buffers;
CLEAR(req); req.count = ; //先要想内核申请buffer缓冲,一般选择4个缓冲,最多是5个
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; if (- == ioctl(fd, VIDIOC_REQBUFS, &req)) //向内核里面设置buffer
{ //分配内存
if (EINVAL == errno)
{
printf("%s does not support memory mapping\n", "ss");
exit(EXIT_FAILURE);
} else
printf("VIDIOC_REQBUFS\n");
} buffers = (buffer *) calloc(req.count, sizeof(buffer)); //分配缓存
if (!buffers)
{
printf("Out of memory\n");
exit(EXIT_FAILURE);
} for (n_buffers = ; n_buffers < req.count; n_buffers++) //将buffer添加到QUERTBUF的队列里面去
{
struct v4l2_buffer buf;
CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers; if (- == ioctl(fd, VIDIOC_QUERYBUF, &buf))
printf("VIDIOC_QUERYBUF"); buffers[n_buffers].length = buf.length; //设置映射方式为mmap
printf("buf.length %d\n", buffers[n_buffers].length);
buffers[n_buffers].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, buf.m.offset); if (MAP_FAILED == buffers[n_buffers].start)
printf("fail mmap\n");
}
return ;
}
bool V4L2::startStream()
{
unsigned int n_buffers;
enum v4l2_buf_type type;
/*将申请到的帧缓冲全部入队列,以便存放采集到的数据*/
for (n_buffers = ; n_buffers < ; n_buffers++)
{
v4l2_buffer buf;
CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers; if (- == ioctl(fd, VIDIOC_QBUF, &buf)) //放入缓存
{
printf("fail VIDIOC_QBUF");
return false;
}
} type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (- == ioctl(fd, VIDIOC_STREAMON, &type)) //打开视频流
{
printf("fail VIDIOC_STREAMON\n");
return false;
} else
printf("StreamOn success!\n");
return true;
}
//摄像头数据 主要是YUYV格式数据,所以需要进行对格式进行转化,转化后的数据保存在AVPicture里面,
//格式是由FMT制定的,输出图像尺寸也是widht_des,height_des决定的
bool V4L2::readFrame(AVPicture & pPictureDes, AVPixelFormat FMT, int widht_des,
int height_des)
{
v4l2_buffer buf;
AVPicture pPictureSrc;
SwsContext * pSwsCtx;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (- == ioctl(fd, VIDIOC_DQBUF, &buf)) //读取
{
printf("fail VIDIOC_DQBUF\n");
return false;
}
pPictureSrc.data[] = (unsigned char *) buffers[buf.index].start;
pPictureSrc.data[] = pPictureSrc.data[] = pPictureSrc.data[] = NULL;
pPictureSrc.linesize[] = fmt.fmt.pix.bytesperline;
int i = ;
for (i = ; i < ; i++)
{
pPictureSrc.linesize[i] = ;
}
pSwsCtx = sws_getContext(width, height, PIX_FMT_YUYV422, widht_des,
height_des, FMT,
SWS_BICUBIC, , , );
int rs = sws_scale(pSwsCtx, pPictureSrc.data, pPictureSrc.linesize, ,
height, pPictureDes.data, pPictureDes.linesize);
if (rs == -)
{
printf("Can open to change to des image");
return false;
}
sws_freeContext(pSwsCtx);
if (- == ioctl(fd, VIDIOC_QBUF, &buf)) //放回缓存
{
printf("fail VIDIOC_QBUF\n");
return false;
}
return true;
} bool V4L2::stopStream()
{
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (- == ioctl(fd, VIDIOC_STREAMOFF, &type))
{
perror("Fail to ioctl 'VIDIOC_STREAMOFF'");
//exit(EXIT_FAILURE);
return false;
}
return true;
}
上面的代码 主要是实现了摄像头的数据捕获,值得注意是函数bool V4L2::readFrame(AVPicture & pPictureDes, AVPixelFormat FMT, int widht_des,
int
height_des);这个是第一个参数 AVPicture是ffmpeg里面的一个结构体 主要是保存图像的。不过这个参数的初始化在
函数外面,这个大家可以看看那篇swscale的文章就知道了,还有一点就是在函数内部分配的栈数据会在函数结束后销毁的。
/*
* H264Encode.cpp
*
* Created on: 2014年1月4日
* Author: ny
*/ #include <H264Encode.h> H264Encode::H264Encode()
{
i_pts = ;
x264EnCoder = NULL;
pPicOut = NULL;
nnal=;
nals=NULL;
} H264Encode::~H264Encode()
{
} void H264Encode::x264_init(AVPicture picture, int width, int height)
{
x264_param_default_preset(¶m, "veryfast", "zerolatency"); param.i_width = width;
param.i_height = height;
param.i_fps_num = ;
param.i_fps_den = ; param.i_keyint_max = ;
param.b_intra_refresh = ; param.b_annexb = ; x264_param_apply_profile(¶m, "baseline");
x264EnCoder = x264_encoder_open(¶m); x264_picture_alloc(&xPic, X264_CSP_I420, width, height); xPic.img.plane[] = picture.data[];
xPic.img.plane[] = picture.data[];
xPic.img.plane[] = picture.data[];
pPicOut = new x264_picture_t;
} void H264Encode::x264_encode()
{
xPic.i_pts = i_pts++;
x264_encoder_encode(x264EnCoder, &nals, &nnal, &xPic, pPicOut);
}
这部分代码 没什么好讲的 就是这个流程,值得注意的是一帧图像编码后可能生成几个nal,nals是一个nal的指针,nnal表示这帧数据有几个nal。而nal里面保存了数据信息。
/*
* H264OnDemandServerMediaSubsession.cpp
*
* Created on: 2014年1月4日
* Author: ny
*/ #include <H264OnDemandServerMediaSubsession.h>
#include <V4L2FramedSource.h>
#include <live/H264VideoStreamFramer.hh>
#include <live/H264VideoRTPSink.hh> H264OnDemandServerMediaSubsession::H264OnDemandServerMediaSubsession(
UsageEnvironment& env, FramedSource * source) :
OnDemandServerMediaSubsession(env, true)
{
mp_source = source;
mp_sdp_line = NULL;
mp_dummy_rtpsink = NULL;
m_done = ;
} H264OnDemandServerMediaSubsession::~H264OnDemandServerMediaSubsession()
{
} void H264OnDemandServerMediaSubsession::chkForAuxSDPLine1()
{
if (mp_dummy_rtpsink->auxSDPLine())
m_done = 0xff;
else
{
int delay = * ; // 100ms
nextTask() = envir().taskScheduler().scheduleDelayedTask(delay, chkForAuxSDPLine, this);
}
} const char * H264OnDemandServerMediaSubsession::getAuxSDPLine(RTPSink *sink,
FramedSource *source)
{
if (mp_sdp_line)
return mp_sdp_line; mp_dummy_rtpsink = sink;
mp_dummy_rtpsink->startPlaying(*source, , );
//mp_dummy_rtpsink->startPlaying(*source, afterPlayingDummy, this);
chkForAuxSDPLine(this);
m_done = ;
envir().taskScheduler().doEventLoop(&m_done);
mp_sdp_line = strdup(mp_dummy_rtpsink->auxSDPLine());
mp_dummy_rtpsink->stopPlaying(); return mp_sdp_line;
} RTPSink * H264OnDemandServerMediaSubsession::createNewRTPSink(Groupsock *rtpsock, unsigned char type, FramedSource *source)
{
return H264VideoRTPSink::createNew(envir(), rtpsock, type);
} FramedSource * H264OnDemandServerMediaSubsession::createNewStreamSource( unsigned sid, unsigned &bitrate)
{
bitrate = ;
return H264VideoStreamFramer::createNew(envir(), mp_source);
}
char const* H264OnDemandServerMediaSubsession::sdpLines()
{
return fSDPLines = (char *)
"m=video 0 RTP/AVP 96\r\n"
"c=IN IP4 0.0.0.0\r\n"
"b=AS:96\r\n"
"a=rtpmap:96 H264/90000\r\n"
"a=fmtp:96 packetization-mode=1;profile-level-id=000000;sprop-parameter-sets=H264\r\n"
"a=control:track1\r\n";
}
H264OnDemandServerMediaSubsession类主要是继承了H264OnDemandServerMediaSubsession类。主要是为了设置SDP 描述。
sdpLines()这个函数 需要根据自己的实际情况进行改写的。关于里面的含义,我这里就不多解释了,熟悉一下RTSP协议就可以完全知道含义了。
/*
* V4L2FramedSource.cpp
*
* Created on: 2014年1月4日
* Author: ny
*/ #include <V4L2FramedSource.h> int V4L2FramedSource::nalIndex = ; V4L2FramedSource::V4L2FramedSource(UsageEnvironment & env) :
FramedSource(env)
{
v4l2 = new V4L2();
pEncode = new H264Encode();
mp_token = NULL;
printf("creater\n");
v4l2->initDev("/dev/video0", , );
avpicture_alloc(&Picture, PIX_FMT_YUV420P, v4l2->getWidth(), v4l2->getHeight());
v4l2->startStream(); pEncode->x264_init(Picture, , );
} V4L2FramedSource::~V4L2FramedSource()
{
} unsigned V4L2FramedSource::maxFrameSize() const
{
return * ;
} void V4L2FramedSource::doGetNextFrame()
{
/*double delay = 1000.0 / 25;
int to_delay = delay * 1000; // us
mp_token = envir().taskScheduler().scheduleDelayedTask(to_delay,
getNextFrame, this);*/
if (V4L2FramedSource::nalIndex == pEncode->nnal)
{
v4l2->readFrame(Picture, PIX_FMT_YUV420P, v4l2->getWidth(), v4l2->getHeight());
pEncode->x264_encode();
V4L2FramedSource::nalIndex = ;
gettimeofday(&fPresentationTime, NULL);
}
memmove(fTo,
pEncode->nals[V4L2FramedSource::nalIndex].p_payload,
pEncode->nals[V4L2FramedSource::nalIndex].i_payload);
fFrameSize = pEncode->nals[V4L2FramedSource::nalIndex].i_payload;
V4L2FramedSource::nalIndex++;
afterGetting(this);
} void V4L2FramedSource::getNextFrame1()
{ } /*
V4L2FramedSource类继承了FramedSource类。V4L2FramedSource是我们自定义的类,主要实现了我们的视频数据如何进入到live555里面去。首先在构造函数里面,我们对v4l2进行了初始化以及x264编码初始化。这个类最重要的就是<SPAN style="BACKGROUND-COLOR: rgb(240,240,240)">doGetNextFrame函数,live555就是通过这个函数将我们的一个nal数据加载到live555里面,然后消息循环发送出去的。我们先是将v4l2捕捉的视频数据进行H264压缩编码,这里面值得注意的是一帧图像可能压缩成几个nal的,所以我这里面在确保一帧数据完全发送完了才向v4l2要数据。然后就是数据的般移了,其中数据存在fTo里面。然后就是消息了。
*/ /*
* Application.cpp
*
* Created on: 2014年1月4日
* Author: ny
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> #include "H264OnDemandServerMediaSubsession.h"
#include "V4L2FramedSource.h" #include <live/liveMedia.hh>
#include <live/BasicUsageEnvironment.hh>
#include <live/UsageEnvironment.hh> UsageEnvironment* env;
static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
char const* streamName, char const* inputFileName = "Live"); // fwd
int main()
{
// 设置使用环境。Begin by setting up our usage environment:
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
env = BasicUsageEnvironment::createNew(*scheduler);
UserAuthenticationDatabase* authDB = NULL; RTSPServer* rtspServer = RTSPServer::createNew(*env, , authDB);
if (rtspServer == NULL)
{
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
exit();
} char const* descriptionString =
"Session streamed by \"testOnDemandRTSPServer\"";
char const* streamName = "live";
ServerMediaSession* sms = ServerMediaSession::createNew(*env, streamName,
streamName, descriptionString);
sms->addSubsession(new H264OnDemandServerMediaSubsession(*env,
new V4L2FramedSource(*env)));
rtspServer->addServerMediaSession(sms);
announceStream(rtspServer, sms, streamName);
env->taskScheduler().doEventLoop(); // does not return return ;
}
static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
char const* streamName, char const* inputFileName)
{
char* url = rtspServer->rtspURL(sms);
UsageEnvironment& env = rtspServer->envir();
env << "\n\"" << streamName << "\" stream, from the file \""
<< inputFileName << "\"\n";
env << "Play this stream using the URL \"" << url << "\"\n";
delete[] url;
}
/*
这是我们程序的主程序,主要是参考了live555的testOnDemandRTSPServer.cpp。主要是区别是我们自己指定了rtsp链接了,以及我们自己实现的framesource。这样本地的视频采集发送服务器就完成了,可以用开源的VLC播放进行播放了,live555支持1对多的,已经测试过可以在ipad,iphone,android同步播放,延迟在1s以内。不过要VLC设置缓存时间在200ms就可以了。
*/
【转】V4L2+swscale+X264+live555实现流媒体服务端的更多相关文章
- 基于v4l2 ffmpeg x264的视频远程监控(附上编译好的库文件)
说明:主要是基于ghostyu网友整理的< arm mini2440 基于v4l2 ffmpeg x264的视频远程监控>.自己做了一遍,遇到不少问题,就整理记录下来. 1.平台 硬件:a ...
- Live555 分析(二):服务端
live555支持单播和组播,我们先分析单播的流媒体服务端,后面分析组播的流媒体服务端. 一.单播的流媒体服务端: // Create the RTSP server: RTSPServer* rts ...
- 庖丁解牛-----Live555源码彻底解密(RTP打包)
本文主要讲解live555的服务端RTP打包流程,根据MediaServer讲解RTP的打包流程,所以大家看这篇文章时,先看看下面这个链接的内容; 庖丁解牛-----Live555源码彻底解密(根据M ...
- live555学习之基本类介绍及计划任务深度探讨
liveMedia项目的源代码包括四个基本的库,各种测试代码以及Media Server.四个基本的库分别是: UsageEnvironment&TaskScheduler, groupsoc ...
- Live555 实战之框架简单介绍
作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 上一篇文章简要介绍了怎样以共享库的方式交叉编译Live555,今天再来介绍live源代码框架. 先 ...
- Live555 分析(一):类介绍
从程序的结构来看,live项目包括了四个基本库.程序入口类(在mediaServer中)和一些测试代码(在testProgs中). 四个基本静态库是UsageEnvironment.BasicUsag ...
- live555 基本框架
(转) 从程序的结构来看,live项目包括了四个基本库.程序入口类(在mediaServer中)和一些测试代码(在testProgs中).四个基本库是UsageEnvironment,BasicUsa ...
- ffmpeg用法小结,教你抓各大网站视频
最近受邀朋友帮忙需要抓取一段某酷电影,偶然间发现ffmpeg程序.说到此可能会有人提到you-get和youtube-dl,期间也接触了该两款程序,但是由于版权原因,该软件仅仅可以抓取前几分钟预览版, ...
- 视频直播技术-视频-编码-传输-秒开等<转>
转载地址:http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=2653547042&idx=1&sn=26d8728548 ...
随机推荐
- iOS 3DES加密 和 java 3DES 解密
首先进入头文件: #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h> #i ...
- mycat实例(1)
2016二月 22 置原 MyCat - 使用篇(1) 分类:数据库分库分表(Mycat等) (1126) (1) 数据库路由中间件MyCat - 使用篇(1) 基本概念 直接介绍概念太枯燥了,还是拿 ...
- 在WebBrowser中执行javascript脚本的几种方法整理(execScript/InvokeScript/NavigateScript) 附完整源码
[实例简介] 涵盖了几种常用的 webBrowser执行javascript的方法,详见示例截图以及代码 [实例截图] [核心代码] execScript方式: 1 2 3 4 5 6 7 8 9 1 ...
- 怎样实现IOS开发中的数据存储方式
iOS 开发中,一般有如下几种数据存储方式.需要根据具体的业务场景,选择 合适的数据存储方式. (1) 用户默认设置 – 这种情况通常不需要用户干预,如游戏通关信息,Video 播放记录,或者 Ap ...
- 小贝_mysql建表以及列属性
mysql建表以及列属性 简要: 一.建表原则 二.具体的列属性说明 一.建表原则 建表: 事实上就是声明列的过程,数据终于是以文件的形式放在硬盘(内存) 列: 不同的列类型占的空间不一样. 选列的原 ...
- CCA概述和安装
什么是CCA? 客户关怀加速器(CCA)为微软动态®CRM通过集中的客户信息从不同的系统在一个集成代理桌面促进剂的效率和有效性. CCA是一个參考应用,利用用户界面集成(UII)为微软Dynamics ...
- Android应用程序键盘(Keyboard)消息处理机制分析
在Android系统中,键盘按键事件是由WindowManagerService服务来管理的,然后再以消息的形 式来分发给应用程序处理,不过和普通消息不一样,它是由硬件中断触发的:在上一篇文章< ...
- Git服务器 gitweb与gitLab的区别
昨天我们已经把Git服务器搭建完成了,工程的上传与下载都可以了,不过有些人不喜欢使用git命令进行操作.所以我们就搭建一个可视化操作的环境!配置gitweb和gitlab两种访问方式! 一,配置git ...
- EffectiveC#5--始终提供ToString()
1.System.Object版的ToString()方法只返回类型的名字 2.知道要重写它,返回更有意义的信息,最好是提供几个重载版本. 3.当你设计更多的复杂的类型时(格式化文本)应该实现应变能力 ...
- 关于.net 对.manifest清单文件查找缓存的猜想
问题背景: winform调用unity web player 插件. 按如下操作: ,编译后会生成.manifest清单文件: 通过清单内容可以看出程序在运行时是按照以上信息来查找ActiveX控件 ...