实例 海康SDK流 转 浏览器 MSE
转为 Media Source Extensions API支持的
最关键的是将海康sdk 预览回调的数据, 转为FMP4格式, Chrome, Firefox等浏览器原生 Media Source Extensions API 可支持的直播流
C++ 关键代码
关键代码
//内存输入数据
int fill_buffer_in(void * opaque,uint8_t *buf, int bufsize){
WebSocketPlayer *instance = static_cast<WebSocketPlayer*>( opaque);
callback_data * data = instance->sDKSource2->takeDataPacketSync();
memcpy( buf, data->data, data->size);//复制到容器 给 ffmpeg
int size = data->size;
SDKSource2::destoryDataPacket(data);//销毁
return size;
}
//内存输出数据
int fill_buffer_out(void * opaque, uint8_t *buf, int bufsize){
WebSocketPlayer *instance = static_cast<WebSocketPlayer*>( opaque);
// callback_data * data = new callback_data;
// data->size = bufsize;
// memcpy( buf, data->data, data->size);
// instance->onAVPacketOutData(data);
instance->onOutData(buf, bufsize);//websocket 推送出去
return bufsize;
}
QVariantMap param;
this->sDKSource2->initSource(param);
int ret =-1;
//////////////////////////////////////////////////////////////////////////
//内存输入
avFormat = avformat_alloc_context();
unsigned char* in_buffer = (unsigned char*)av_malloc(40690);
AVIOContext* in_avio = avio_alloc_context(in_buffer , 40690 ,0 , this, fill_buffer_in,nullptr,nullptr);
assert( in_avio != NULL);
avFormat->pb = in_avio;
//open input
if ( ( avformat_open_input(&avFormat, nullptr,nullptr, nullptr) ) != 0) {
qDebug()<<"Could not open input .";
return ;
}
//获取流信息
if((avformat_find_stream_info(avFormat, 0) )!=0){
qDebug()<<"find stream fail,获取输入信息 失败";
}
//输出格式 看看
// av_dump_format(avFormat, 0, "", 0);
//流通道
for(int i=0; i<avFormat->nb_streams; i++) {
if(avFormat->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoIndex = i;
qDebug()<<"av index="<<i;
}else if(avFormat->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
qDebug()<<"ai index="<<i;
}
}
//转码 // 内存输出
unsigned char* out_buffer = (unsigned char*)av_malloc(40690);
AVIOContext* out_avio = avio_alloc_context(out_buffer , 40690 , 1 , this, nullptr,fill_buffer_out,nullptr);
assert( out_avio != NULL);
AVOutputFormat *oformat = av_guess_format("mp4", nullptr, nullptr);
ret = avformat_alloc_output_context2(&outAvFormatCtx,oformat,nullptr,nullptr);
outAvFormatCtx->pb = out_avio;0.
outAvFormatCtx->flags = AVFMT_FLAG_CUSTOM_IO;
if (ret < 0) {
qDebug()<<": alloc output ctx error 申请ctx创建出错";
throw ("Could not create output context");
}
outAVstream = avformat_new_stream(outAvFormatCtx, avFormat->streams[videoIndex]->codec->codec);
if (!outAVstream) {
qDebug()<<" output stream channel create error 创建输出通道出错";
throw ("Failed allocating output stream");
}
ret = avcodec_copy_context(outAVstream->codec, avFormat->streams[videoIndex]->codec);
if (ret < 0) {
qDebug()<<": copy context error 出错";
throw ("Failed to copy context from input to output stream codec context");
}
//标记初值.
outAVstream->codec->codec_tag = 0;
if (outAvFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
outAVstream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
qDebug()<<"open out..";
//open out
// avio_open(&outAvFormatCtx->pb, "nothing",AVIO_FLAG_WRITE) ;
// if (!outAvFormatCtx->pb ) {
// qDebug()<<"错误: format->pd is NULL";
// throw ("Error open ouput fail ");
// }
///写头 FMP4 格式封装
AVDictionary* options = nullptr;
av_dict_set(&options, "movflags", "empty_moov+default_base_moof",0);
av_dict_set_int(&options, "frag_duration", 200 * 1000, 0);
ret = avformat_write_header(outAvFormatCtx, &options);//写头
qDebug()<<"avformat_write_header .";
if (ret < 0) {
char * errMesg = new char[512];
av_strerror(ret,errMesg,512);
std::cout<<" write header err:"<<errMesg<<std::endl;
delete[]errMesg;
throw ("Error occurred when write header");
}
//复用
AVFrame *pFrame = av_frame_alloc();
AVPacket packet;
av_init_packet(&packet);
int count= 0;
qDebug()<<" push data is ready.";
//
while(runFlag) {
ret = av_read_frame(avFormat, &packet);
if(packet.stream_index != videoIndex){
//qDebug()<<"非视频帧";
av_packet_unref(&packet);
continue;
}
count++;
if(count %10 == 0){
qDebug()<<" REV video packet "<<count<<" count";
}
/* copy packet 转换PTS/DTS(Convert PTS/DTS)*/
AVStream *in_stream, *out_stream;
in_stream = avFormat->streams[packet.stream_index];
out_stream = outAvFormatCtx->streams[packet.stream_index];
packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
packet.pos = -1;
//不解码 写原始内容
ret = av_write_frame (outAvFormatCtx, &packet);//直接将包写进Mux, 没有缓存, 排序, 检验DTS
//ret = av_interleaved_write_frame(outAvFormatCtx, &pkt);//这个 有缓存, 排序, 检验DTS 会导致延迟
if (ret < 0) {
char * errMesg = new char[256];
av_strerror(ret,errMesg,256);
std::cout<<" push fail "<<ret<<errMesg<<std::endl;
delete[] errMesg;
msleep(100);
}
av_packet_unref(&packet);
}
//测试代码, 写尾释放 packet 等
html 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="jMuxer - a simple javascript mp4 muxer for non-standard streaming communications protocol">
<meta name="keywords" content="h264 player, mp4 player, mse, mp4 muxing, jmuxer, aac player">
<title> stream demo</title>
</head>
<body>
<div id="container" style="margin: 0 auto; text-align: center;">
<div style="padding: 5px;">
<button onclick="player()">player</button>
<button onclick="fb()">-1S</button>
<button onclick="fs()">+1S</button>
<button onclick="debugg()">test</button>
</div>
<video style="border: 1px solid #333; max-width: 600px;height: 400px;"
controls autoplay poster="images/loader-thumb.jpg" id="player" muted></video>
</div>
<script>
var chunks = [];
var video = document.getElementById('player');
var mse = new (MediaSource || WebKitMediaSource)();
var sourceBuffer;
video.src = URL.createObjectURL(mse);
mse.addEventListener('sourceopen', onMediaSourceOpen);
function onMediaSourceOpen() {
//MIME = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
sourceBuffer = mse.addSourceBuffer('video/mp4; codecs="avc1.4d401f"');
sourceBuffer.addEventListener('updateend', addMoreBuffer);
video.play();
}
function addMoreBuffer() {
if (sourceBuffer.updating || !chunks.length) {
return;
}
sourceBuffer.appendBuffer(chunks.shift());
}
function player(){
var socketURL = 'ws://localhost:8084';
//var socketURL = 'ws://localhost:3265/open/api/we';
var ws = new WebSocket(socketURL);
ws.binaryType = 'arraybuffer';
ws.addEventListener('message',function(event) {
chunks.push(new Uint8Array(event.data));
addMoreBuffer();
});
ws.addEventListener('error', function(e) {
console.log('Socket Error');
});
}
function fs(){
video.currentTime = video.currentTime+1
}
function fb(){
video.currentTime = video.currentTime-1
}
function debugg(){
console.log(video)
//video.currentTime = sourceBuffer.buffered.end(0)
}
</script>
</body>
</html>但是浏览器貌似有缓存… 延迟7,8秒, 可以通过video.currentTime控制, 但是还有2s左右延迟..
最终可以使用 video.currentTime = sourceBuffer.buffered.end(0) 直接设置跳到最新的缓存帧时间, 延迟在半秒左右!
Transclude of wcl-video-server.zip
转为 FLV.js 支持的
只需修改 oformat 格式, 并且 avformat_write_header 的 options去掉
AVOutputFormat *oformat =
av_guess_format("flv", nullptr, nullptr);
//av_guess_format("mp4", nullptr, nullptr);
ret = avformat_alloc_output_context2(&outAvFormatCtx,oformat,nullptr,nullptr);
AVDictionary* options = nullptr;
/* fmp4封装
av_dict_set(&options, "movflags", "empty_moov+default_base_moof",0);
av_dict_set_int(&options, "frag_duration", 0, 0);
av_dict_set_int(&options, "min_frag_duration", 0, 0);
av_dict_set_int(&options, "frag_size", 0, 0); */
////
ret = avformat_write_header(outAvFormatCtx, &options);//写头
videoTagDocment.currentTime = player.buffered.end(0)
可以通过 videoTagDocment.currentTime控制, 但是仍然有2s左右延迟..
这两个底层解码原理是一样的…