最近遇到个需求,将私有协议的码流,就是比较老的视频设备啦,新设备都支持标准H264,H265了,或者私有平台协议的视频,将这些私有协议视频通过转码推送到标准的流媒体服务器,然后通过网页不使用插件进行。目前无插件播放的播放器很多比如video.js啦,一般只支持H264的flv格式,HLS等,当然也有支持H265的,不过这些都不是重点,今天的重点是怎么通过FFMPEG将私有协议的码流转化为标准的H264然后通过FFMPEG推送到流媒体服务器,从而通过网页直接播放。
这个需求嘛,做的方式也很多,比如先对接私有SDK,然后通过私有解码库解码为YVU然后在编码转发,这是一个比较普遍的做法,也很常规。不过其实比较麻烦, 需要对编解码有一定基础。下面我介绍的方法较为简单,也很通用,只需要私有SDK支持抓图功能即可,一般私有SDK抓图的接口基本都有。
主要思路就是使用私有SDK的抓图接口,不停的抓图,然后将图片数据输送到FFMPEG的管道,这样就可以源源不断的推送了。
首先是FFMPEG的管道命令使用方法
ffmpeg.exe -f image2pipe -i \\.\\pipe\MyPipe -pix_fmt yuv420p -vcodec libx264 -f rtsp -rtsp_transport tcp "rtsp://127.0.0.1:8554/1234/1"
调用这个命令后,FFMPEG就会连接\\.\\pipe\MyPipe的命名管道,如果没有打开命名管道,FFMPEG会退出,如果有,则会等待管道数据,我们就是利用此功能进行推送
好了,废话不多说,上干货,也就是代码,这里以windows为例,linux大同小异就是管道的使用方法不同而已,大家学会举一反三
long StartProgress()//调用FFMPEG命令推流
{
char sProcessID[32] = {0};
sprintf_s(sProcessID,sizeof(sProcessID),"%d",GetCurrentProcessId());
char sparam[1024];
//调用FFMPEG命令通过管道数据推流,这里想推什么流改后面的推流地址即可,FFMPEG很强大的
sprintf(sparam,"ffmpeg.exe -f image2pipe -i \\\\.\\pipe\\MyPipe -pix_fmt yuv420p -vcodec libx264 -f rtsp -rtsp_transport tcp \"rtsp://127.0.0.1:8554/1234/1\"");
PROCESS_INFORMATION pi;
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW;
si.lpTitle = "";
si.wShowWindow = SW_SHOWNORMAL;
DWORD dwExitCode =0;
BOOL ret = ::CreateProcess(NULL,(char*)sparam, NULL, NULL, FALSE,CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (ret)
{
//m_lpid = pi.dwProcessId;
//GF_AddProcessToJobObject(pi.hProcess);
//关闭子进程的主线程句柄
//等待子进程的退出
//WaitForSingleObject(pi.hProcess,INFINITE);
//获取子进程的退出码
//GetExitCodeProcess(pi.hProcess, &dwExitCode);
//::CloseHandle((HANDLE)pi.dwProcessId);
::CloseHandle((HANDLE)pi.hThread);
//关闭子进程句柄
//CloseHandle(pi.hProcess);
}else
{
//LOGE << "idx:" << m_lidx << " Start Progress Failed.error:" << GetLastError();
}
return 0;
}
int cappic()//抓图函数
{
CString str;
CString strDIR;
int num = 1;
int nDir = 0;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;
if( InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) )
{
// add a NULL disc. ACL to the security descriptor.
if (SetSecurityDescriptorDacl(&sd, TRUE, (PACL) NULL, FALSE))
{
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor =&sd;
sa.bInheritHandle = TRUE;
//创建一个命名管道,在windows中\代表zhuan'yi两个\\代表一个\
HANDLE hNamedPipe = CreateNamedPipeA("\\\\.\\pipe\\MyPipe",
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE, 1, 1024, 1024,0 , &sa);
//检查是否创建成功
if (hNamedPipe == INVALID_HANDLE_VALUE)
{
printf("create named pipe failed!\n");
}
else
{
printf("create named pipe success!\n");
}
//异步IO结构
OVERLAPPED op;
ZeroMemory(&op, sizeof(OVERLAPPED));
//创建一个事件内核对象
op.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
//等待一个客户端进行连接
BOOL b = ConnectNamedPipe(hNamedPipe, &op);
//开启FFMPEG,并调用命令
StartProgress();
//当有客户端进行连接时,事件变成有信号的状态
if (WaitForSingleObject(op.hEvent, INFINITE) == 0)
{
printf("client connect success!\n");
}
else
{
printf("client connect failed!\n");
}
unsigned char* buff = new unsigned char[1024*1024*100];
memset(buff,0,1024*1024*100);
CFile file;
while(TRUE)
{
if(m_lPlayBackHandle == -1)
return -1;
//下面的函数就是私有SDK抓图函数,自己改为自己的抓图接口即可
str.Format("G:\\test\\conver\\bin\\1234\\1.bmp",num);
GF_CreateDir(str);
int nRes = EX_NET_CapturePicture(0,m_lPlayBackHandle,(char*)(LPCTSTR)str);
//--------抓图接口抓图完成后,读取图像数据,通过管道输入到FFMPEG
if(file.Open(str,CFile::modeReadWrite))
{
memset(buff,0,1024*1024*100);
int nLen = file.Read(buff,1024*1024*100);
DWORD cbWrite;
WriteFile(hNamedPipe, buff, nLen, &cbWrite, NULL);
file.Close();
}
Sleep(10);
}
}
}
return 0xFFFF;
}
效果图上个
说明推流成功啦
VLC播放效果
CPU占用略高,FFMPEG转码占用CPU本身比较高
我I5的CPU大概解码15%,编码15%左右
此文仅用来抛砖引玉,给出个思路,具体应用到项目中,还需很多工作
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)