使用vlc3.0.6 在ubuntu 64bit上编译,vlc使用插件的方式组织对多种视频源的支持,比如 avi、mp4、mkv、等等,这里想添加一个自己的demuxer,从一个h.264文件中读h264数据,播放。(vlc并不支持直接播放h264裸流文件,至少这个v3.0.6版本是没有支持)
一:添加一个demuxer模块:
在moudle/demux 的makefile.am 中添加:
libwangscr_plugin_la_SOURCES = demux/wang_test/h264src.c
libwangscr_plugin_la_LIBADD = $(LIBM)
libwangscr_plugin_la_LDFLAGS = $(AM_LDFLAGS)
demux_LTLIBRARIES += libwangscr_plugin.la
然后到源码顶层目录,重新 # ./configure #make
demux/wang_test/h264src.是 自己要添加的插件实现文件
libwangscr 是自己的插件名
最后会得到在 /modules/.libs 目录下得到 libwangscr_plugin.la
贴上插件代码h264src.c的实现:
前半部分有从h264文件获取流数据的实现:
#define PLAY_FPS 30 //播放的帧率,可自由设置,(不嫌麻烦可以从h264流的sps中解码出来,让视频正常速度播放)
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include<stdio.h>
#include <vlc_demux.h>
#include <vlc_charset.h> /* EnsureUTF8 */
#include <vlc_input.h>
#include <vlc_aout.h>
#include <vlc_plugin.h>
#include <sys/time.h>
#define BUFFER_TIME_MS 500 //缓冲500ms的数据
#define PLAY_FPS 30 //播放的帧率,可自由设置,(不嫌麻烦可以从h264流的sps中解码出来,让视频正常速度播放)
/***
***20190828 canok
*** output: complete frames
**/
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <string.h>
#define MIN(a,b) ((a)<(b)?(a):(b))
typedef unsigned char uint8_t; //无符号8位数
#define NALU_TYPE_SLICE 1
#define NALU_TYPE_DPA 2
#define NALU_TYPE_DPB 3
#define NALU_TYPE_DPC 4
#define NALU_TYPE_IDR 5
#define NALU_TYPE_SEI 6
#define NALU_TYPE_SPS 7
#define NALU_TYPE_PPS 8
#define NALU_TYPE_AUD 9
#define NALU_TYPE_EOSEQ 10
#define NALU_TYPE_EOSTREAM 11
#define NALU_TYPE_FILL 12
#define CACH_LEN (1024*200)//缓冲区不能设置太小,如果出现比某一帧比缓冲区大,会被覆盖掉一部分
static uint8_t *g_cach[2] = {NULL,NULL};
static FILE* fp_inH264 = NULL;
static int icach = 0;
static int ioffset = 0;
static int bLoop = 1;
static int init()
{
if(g_cach[0] == NULL)
{
g_cach[0] = (uint8_t*)malloc(CACH_LEN);
}
if(g_cach[1] == NULL)
{
g_cach[1] = (uint8_t*)malloc(CACH_LEN);
}
if(fp_inH264 == NULL)
{
fp_inH264 = fopen("./test_move.264","r");
if(fp_inH264 == NULL)
{
printf("fope erro [%d%s]\n",__LINE__,__FUNCTION__);
return -1;
}
}
if(fread(g_cach[icach], 1,CACH_LEN,fp_inH264 )<CACH_LEN)
{
printf("intpufile too short [%d%s]\n",__LINE__,__FUNCTION__);
return -1;
}
return 0;
}
static int deinit()
{
if(g_cach[0])
{
free(g_cach[0]);
g_cach[0] = NULL;
}
if(g_cach[1])
{
free(g_cach[1]);
g_cach[1] = NULL;
}
if(fp_inH264)
{
fclose(fp_inH264);
fp_inH264 = NULL;
}
}
static int checkNal(uint8_t nalHeader)
{
char type = nalHeader & ((1<<5)-1);
switch(type)
{
case NALU_TYPE_SPS:
printf("sps\n");
break;
case NALU_TYPE_PPS:
printf("pps\n");
break;
case NALU_TYPE_IDR:
printf("I slice !!!!!!!!!!!!!!\n");
break;
case NALU_TYPE_SLICE:
printf("B/P slice\n");
break;
case NALU_TYPE_AUD:
printf("Delimiter==========\n");
break;
default:
printf("type :%d\n",type);
break;
}
return type;
}
static int checkFlag(uint8_t *buffer, int offset)
{
static uint8_t mMark[4] = {0x00,0x00,0x00,0x01};
return !memcmp(buffer+offset,mMark,4);
//return (!memcmp(buffer+offset,mMark,4) && ((buffer[offset+4]&((1<<5)-1)) == 9) );
}
//获取一个Nal到 buf, bufLen表示缓冲区最大可以容纳的数据
//返回实际的帧数据长度
static int getOneFrame(uint8_t *buf, int bufLen)
{
int i =0;
int startpoint = ioffset;
int endpoint = ioffset;
for (i = ioffset+4; i <= CACH_LEN - 4; i++) {
if (checkFlag(g_cach[icach], i)){
startpoint = ioffset;
endpoint = i;
break;
}
}
if(endpoint - startpoint > 0)
{
int dataLen = endpoint -startpoint;
if(bufLen < dataLen)
{
printf("recive buffer too short , need %d byte!\n",dataLen);
}
memcpy(buf,g_cach[icach]+startpoint, MIN(dataLen,bufLen));
ioffset = endpoint;
return MIN(dataLen,bufLen);
}
else
{
int oldLen =CACH_LEN -startpoint;
memcpy(g_cach[(icach+1)%2],g_cach[icach]+startpoint, oldLen );
int newLen = 0;
newLen = fread(g_cach[(icach+1)%2]+oldLen, 1,CACH_LEN -(oldLen),fp_inH264);
if(newLen <CACH_LEN -(oldLen) )
{
if(bLoop)
{
fseek(fp_inH264,0,SEEK_SET);
ioffset =0;
icach =0;
fread(g_cach[icach], 1,CACH_LEN,fp_inH264 );
return getOneFrame(buf,bufLen);
}
else
{
return -1;
}
}
ioffset = 0;
icach = (icach+1)%2;
return getOneFrame(buf,bufLen);
}
}
/*****************************************************************************
* Module descriptor
*****************************************************************************/
static int Open ( vlc_object_t * );
static void Close( vlc_object_t * );
vlc_module_begin ()
set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_DEMUX )
set_description( N_("android src stream demuxer") )
set_shortname( N_("screen") )
set_capability( "access_demux", 0 )
set_callbacks( Open, Close )
vlc_module_end ()
//
static int temp =0;
typedef struct
{
demux_t *p_demux;
es_format_t fmt;
es_out_id_t *p_es;
}stream_track_t;
struct demux_sys_t
{
//记录多路流
int i_track;
stream_track_t **track;
int64_t startTimes;
int count;
char *buffer;
};
#define DEBUG_INFO 0
static int Demux( demux_t *p_demux )
{
#if 1
demux_sys_t *p_sys = p_demux->p_sys;
//在这里获取一帧数据
usleep(1000000/PLAY_FPS);
int len =0;
len = getOneFrame(p_sys->buffer,CACH_LEN);
block_t *p_block;
if( (p_block = block_Alloc( len )) )
{// 填充数据到 block
struct timeval pts;
gettimeofday(&pts, NULL);
int64_t i_pts = (int64_t)pts.tv_sec * INT64_C(1000000) +
(int64_t)pts.tv_usec;
i_pts &= INT64_C(0x00ffffffffffffff);
p_block->i_dts = (i_pts - p_sys->startTimes)+VLC_TS_0;
p_block->i_pts = p_block->i_dts;
#if DEBUG_INFO
struct timeval test_time;
struct tm *st_tm = NULL;
gettimeofday(&test_time,NULL);
st_tm = gmtime(&test_time.tv_sec);
printf(" len %08d [%02d:%02d:%02d:%06d] ipts:%ld idts:%ld \n",len,st_tm->tm_hour,st_tm->tm_min,st_tm->tm_sec,test_time.tv_usec,p_block->i_pts,p_block->i_dts);
#endif
if((p_sys->count++)== (int)(BUFFER_TIME_MS* PLAY_FPS/1000 + 1) )
{//需要在缓冲区缓冲完后,这里更新一下参考时钟,
//否则可能出现只解一帧的情况
//只能手动大致计算下了
es_out_SetPCR( p_demux->out, p_block->i_dts );
}
memcpy( p_block->p_buffer, p_sys->buffer, len);
}
//这里只有一个 stream, 发送到track[0] 就行
es_out_Send( p_demux->out,p_sys->track[0]->p_es, p_block );
#endif
return VLC_DEMUXER_SUCCESS;
}
static int Control( demux_t *p_demux, int i_query, va_list args )
{
#if DEBUG_INFO
printf("screen control i_query %#lx [%d%s%s]\n",i_query,__LINE__,__FUNCTION__,__FILE__);
#endif
bool *pb;
int64_t *pi64, i64;
double *pf, f;
demux_sys_t *p_sys = p_demux->p_sys;
switch(i_query)
{
case DEMUX_CAN_PAUSE:
case DEMUX_CAN_SEEK:
{
pb = va_arg( args, bool * );
*pb = false;
return VLC_SUCCESS;
}
break;
case DEMUX_CAN_CONTROL_PACE:
{
pb = va_arg( args, bool * );
*pb = false;
return VLC_SUCCESS;
}
break;
case DEMUX_CAN_CONTROL_RATE:
{
pb = va_arg( args, bool * );
*pb = false ;
return VLC_SUCCESS;
}
break;
#if 0
case DEMUX_GET_FPS:
pf = va_arg( args, double * );
*pf = 30;
return VLC_SUCCESS;
#endif
case DEMUX_GET_PTS_DELAY:
pi64 = va_arg( args, int64_t * );
#if 0
*pi64 = INT64_C(1000)
* var_InheritInteger( p_demux, "network-caching" );
#endif
*pi64 = INT64_C(1000)*500;
return VLC_SUCCESS;
default:
return VLC_EGENERIC;
}
}
static int initVideoES(demux_t *p_demux)
{//es_out 添加一个h264 es流
demux_sys_t *p_sys = p_demux->p_sys;
stream_track_t *tk = (stream_track_t*)malloc( sizeof( stream_track_t ) );
if(tk ==NULL)
{
return -1;
}
tk->p_demux = p_demux;
es_format_Init( &tk->fmt, VIDEO_ES, VLC_CODEC_H264 );
//tk->fmt.video.i_width = 1920;
//tk->fmt.video.i_height = 1080;
tk->p_es = es_out_Add( p_demux->out, &tk->fmt );
if( tk->p_es )
{
TAB_APPEND_CAST( (stream_track_t **), p_sys->i_track, p_sys->track, tk );
}
else
{
es_format_Clean( &tk->fmt );
free( tk );
printf("erro to init VideoEs [%d%s]\n",__LINE__,__FUNCTION__);
return -1;
}
return init();
}
static int Open( vlc_object_t * p_this )
{
//printf("wang screen module open ! [%d%s%s]\n",__LINE__,__FUNCTION__,__FILE__);
demux_t *p_demux = (demux_t*)p_this;
demux_sys_t *p_sys = NULL;
p_demux->pf_demux = Demux;
p_demux->pf_control= Control;
p_demux->p_sys = p_sys = (demux_sys_t*)calloc( 1, sizeof( demux_sys_t ) );
p_sys->buffer = calloc(1,CACH_LEN);
if( !p_sys ) return VLC_ENOMEM;
struct timeval pts;
gettimeofday(&pts, NULL);
int64_t i_pts = (int64_t)pts.tv_sec * INT64_C(1000000) +
(int64_t)pts.tv_usec;
i_pts &= INT64_C(0x00ffffffffffffff);
p_sys->startTimes = i_pts;
p_sys->count = 1;
if(initVideoES(p_demux))
{
printf("wang screen vedioees init erro![%d%s]\n\n\n",__LINE__,__FUNCTION__);
}
//printf("wang screen module open ok! [%d%s%s]\n",__LINE__,__FUNCTION__,__FILE__);
//important !!!!
es_out_SetPCR( p_demux->out, VLC_TS_0 );
return 0;
}
static void Close ( vlc_object_t * p_this )
{
demux_t *p_demux = (demux_t*)p_this;
demux_sys_t *p_sys = p_demux->p_sys;
for( int i = 0; i < p_sys->i_track; i++ )
{
stream_track_t *tk = p_sys->track[i];
es_format_Clean( &tk->fmt );
free( tk );
}
TAB_CLEAN( p_sys->i_track, p_sys->track );
free(p_sys->buffer);
free( p_sys );
deinit();
}
二: 使用vlc库播放:
在 bin/vlc.c main 函数,屏蔽掉源代码,用自己的demo:
int main()
{
/* The so-called POSIX-compliant MacOS X reportedly processes SIGPIPE even
* if it is blocked in all thread.
* Note: this is NOT an excuse for not protecting against SIGPIPE. If
* LibVLC runs outside of VLC, we cannot rely on this code snippet. */
signal (SIGPIPE, SIG_IGN);
/* Restore SIGCHLD in case our parent process ignores it. */
signal (SIGCHLD, SIG_DFL);
printf("wang [%s:%d:%s] \n",__FILE__,__LINE__,__FUNCTION__);
#ifndef NDEBUG
/* Activate malloc checking routines to detect heap corruptions. */
setenv ("MALLOC_CHECK_", "2", 1);
/* Disable the ugly Gnome crash dialog so that we properly segfault */
setenv ("GNOME_DISABLE_CRASH_DIALOG", "1", 1);
#endif
#ifdef TOP_BUILDDIR
setenv ("VLC_PLUGIN_PATH", TOP_BUILDDIR"/modules", 1);
setenv ("VLC_DATA_PATH", TOP_SRCDIR"/share", 1);
#endif
/* Clear the X.Org startup notification ID. Otherwise the UI might try to
* change the environment while the process is multi-threaded. That could
* crash. Screw you X.Org. Next time write a thread-safe specification. */
unsetenv ("DESKTOP_STARTUP_ID");
//=======================================================//
libvlc_instance_t *vlc;
libvlc_media_player_t *media_player_cli;
libvlc_media_t *media_cli;
vlc = libvlc_new(0,NULL);
if(vlc == NULL)
{
printf("wang erro libvlc_new \n");
return -1;
}
media_player_cli = libvlc_media_player_new(vlc);
media_cli = libvlc_media_new_location(vlc,"screen://123");
libvlc_media_player_set_media(media_player_cli,media_cli);
libvlc_media_player_play(media_player_cli);
while(1); //主线程不能退出,不然子线程也挂掉,播放不了了
}
在libvlc_media_new_location(vlc,"screen://123");这一行代码,"screen" 要和 set_shortname( N_("screen") ) 中的描述,至于123这些没什么用,随便加的。 vlc启动的时候会去插件目录里面扫描所有目录,存到一个树中,上述匹配之后vlc创建demuxer的时候会缓存树里面查找对应库,并加载它作为demuxer使用。