mp4-->ffm-->flv的问题描述
====
flags +global_header
codec:h264+aac
container trans: -codec copy
ffmpeg ts --> flv/ffm OK
remux ts --> ffm OK
ffmpeg ts --> http:/feed1.ffm
播放时:
[07:21:02.155] [flv @ 0x1f22e30]Malformed AAC bitstream detected: use audio bitstream filter 'aac_adtstoasc' to fix it ('-bsf:a aac_adtstoasc' option with ffmpeg)
[07:21:02.155] Error writing frame to output for stream 'flv': Invalid data found when processing input
单独视频可以播放:
NoAudio
ffmpeg -re -i xx.ts -map 0:0 -override_ffserver -codec copy -flags +global_header http://localhost:8090/feed1.ffm
rtsp封装无声音:
sdp.c: AAC with no global headers is currently not supported
1. 在解析配置文件时被初始化:
FFStream stream 和AVFormatContext类似,有
OutputFormat *fmt;
int nb_streams;
AVStream streams[];
2. build_feed_stream, 会write_header,写入文件/tmp/feed1.ffm,
但是这个header有问题:unspecified pixel format
(gdb) p feed->streams[1]->codec->pix_fmt
$7 = AV_PIX_FMT_NONE
3. 首次接收到数据时,把该数据的codec复制到c->stream->streams[i]->codec。
4. 首次发数据时,把c->stream->fmt/streams复制到c->fmt_ctx,
然后在fmt_ctx上write_header,写入c->pb_buffer。
remuxing xx.ts --> xx.flv 第4次后:
Malformed AAC bitstream
remuxing xx.mp4 --> xx.flv write_frame:
1. unspecified size, sample format
2. unspecified size
3--20. decoding for stream 0 failed
21之后正常。
remuxing xx.mp4 --> xx.ffm --> xx.flv
missing picture in access unit with size
no frames
unspecified size
ffmpeg xx.mp4 --> xx.ffm --> xx.flv也报错。
ffplay xx.ffm 只有声音,并且也是no frames
ffmpeg xx.mp4 --> xx.flv 正常
remuxing xx.mp4 --> xx.flv 也正常
能不能绕开ffm呢?
像这样
<Stream file.flv>
Format flv
File xx.flv
</Stream>
没有任何报错,可以正常播放。
能不能扩展到实时流呢?
模拟读文件,对于一个新的请求,
要给它发文件头,所以需要缓存文件头。
然后请求包,由于循环缓冲区等原因,会丢包。
remuxing 里面丢几个包试试。
丢前100个包, 可正常播放。
每30个包丢一个,ffplay 没有报错,在那个时间点部分马赛克,其他时间正常播放。
假设用例是这样的:
ffmpeg -i input http://localhost:8090/stream1.flv #只发数据
ffplay http://serverip:8090/stream1.flv #只收数据
ffmpeg为什么要走http?
目的是让ffserver统一管理socket io。
让ffmpeg原封不动的传包, ffserver收到后放到循环缓冲里面。
如果写文件的话,要设计一个ffm类似的文件格式,走老路上了。
收发的文件名一样?
以前不同是因为feed-stream设计是多对一的关系, ffserver要做封包的工作。
现在我们目前只需要一对一的关系,不用重新封包了。
发数据这块,用另外的端口,unix domain socket更好些,但是怎么让
ffmpeg原生地支持:
unix.c
unix://localhost:port/stream1.flv -timeout 1000 -type datagram
为了让ffserver知道数据大小?
到avio http这一层已经没有包的边界了。
如果在fferver端这样:保留http过来的前面几KB数据作为,剩下的循环缓存。
很可能会导致ffplay卡顿。
换个角度,我们把remuxing的read_frame,write_frame放到不同进程。
甚至让文件头信息也共享,这样就没有边界问题了,且不用转换和传输。
具体做法:
把remuxing.c复制两份,使用共享内存,建立一个固定的循环缓冲。
写端只管写,不用管读端;
读端读指针不能超过写指针。
#define PKT_BUF_SIZE 32
typedef struct{
AVFormatContext *ifmt_ctx;
AVFormatContext *ofmt_ctx;
AVPacket *pkt_buf[PKT_BUF_SIZE];
int wpos, rpos;
}share_data_t;
注意我们里面的很多指针也要在共享空间。
但是怎么 malloc from shared mem?
还有一种方案:
不要write_frame,而是avio_write(pkt);
定义一种所有AVFormat的超级格式.sff:
SFFx size data
x = 0表示循环写位置等元数据信息。
x = 1表示是write_header的内容,size 4个字节是后面data的长度。
x = 2表示后面的数据是AVPacket。
"SFF"本身是同步前缀码。
ffserver接收包要缓存,收到完整的包再输出。
因为ffserver里面读到开始的4096字节就read_header,以为codec就全部找到了。
实际上,从先前的demuxing可以看出,还需要读20个包, find_stream_info才能全部找到。
究竟多少个包才够找全呢?
重新设计配置文件。
先剪掉rtp, 以后再加进来。
Feed也不要
凡是关于codec opt都不要。
现在配置文件也不需要了。
剩下的需增加两个功能点:
o POST进来的文件名要记录,用它来关联以后的GET的连接。
o GET连接要维护自己的读指针。
o POST连接也有自己的写指针。
部署问题:
o 已经去掉了不需要的,ffserver在我的64bit ubuntu上仍然有4.4MB,
how to make ffmpeg small ?
o http输出不能chunked
设置输入参数:
av_dict_set(&stream->in_opts, "mpeg2ts_compute_pcr", "1", 0);
avformat_open_input(&infile, stream->feed_filename, stream->ifmt, &stream->in_opts)
设置输出参数:
AVDictionary *opts = NULL;
av_dict_set(&of->opts, "chunked_post", "0", 0);
avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, NULL, &of->opts)
if(s->pb)avio_close(s->pb);
av_dict_free(&of->opts);
o remuxing 异常退出时没有看到ffserver关连接,里面的连接还在。
ffplay ctrl c 中断有, 那说明remuxing没有处理好。
我们处理办法是截获中断信号,avio时会主动查询到会返回AVERROR_EXIT。
其实发现remuxing正常退出,ffserver也没有断开连接,参考原始代码,
把接收长度为零就认为是断开的检查加上后,CLOSE_WAIT就变成正常的TIME_WAIT了。
这种方式,remuxing不用任何改动,就可以处理正常退出和中断退出两种情形。
但是这里有个问题是,remuxing在某种情形下就是不发数据,该怎么兼顾呢?
如果ffserver主动断开ffplay的GET连接,发现ffplay并没有退出,ffplay到ffserver的链路处于CLOSE_WAIT状态。
if(len <= 0)改成if(len < 0) ?
因为POST连接要占用缓存内存也不不小,还是主动断开更好些。
或者heart_beat/timeout/retry_times也可以考虑。
o remuxing 内存占用估计
每次处理一个包都要open_dyn_buf,之后释放掉,能不能只用一个最大的buf呢?
全局avio_open_dyn_buf/avio_close_dyn_buf一次。
每次处理包时,url_resetbuf/存取内部指针。
====
flags +global_header
codec:h264+aac
container trans: -codec copy
ffmpeg ts --> flv/ffm OK
remux ts --> ffm OK
ffmpeg ts --> http:/feed1.ffm
播放时:
[07:21:02.155] [flv @ 0x1f22e30]Malformed AAC bitstream detected: use audio bitstream filter 'aac_adtstoasc' to fix it ('-bsf:a aac_adtstoasc' option with ffmpeg)
[07:21:02.155] Error writing frame to output for stream 'flv': Invalid data found when processing input
单独视频可以播放:
NoAudio
ffmpeg -re -i xx.ts -map 0:0 -override_ffserver -codec copy -flags +global_header http://localhost:8090/feed1.ffm
rtsp封装无声音:
sdp.c: AAC with no global headers is currently not supported
1. 在解析配置文件时被初始化:
FFStream stream 和AVFormatContext类似,有
OutputFormat *fmt;
int nb_streams;
AVStream streams[];
2. build_feed_stream, 会write_header,写入文件/tmp/feed1.ffm,
但是这个header有问题:unspecified pixel format
(gdb) p feed->streams[1]->codec->pix_fmt
$7 = AV_PIX_FMT_NONE
3. 首次接收到数据时,把该数据的codec复制到c->stream->streams[i]->codec。
4. 首次发数据时,把c->stream->fmt/streams复制到c->fmt_ctx,
然后在fmt_ctx上write_header,写入c->pb_buffer。
remuxing xx.ts --> xx.flv 第4次后:
Malformed AAC bitstream
remuxing xx.mp4 --> xx.flv write_frame:
1. unspecified size, sample format
2. unspecified size
3--20. decoding for stream 0 failed
21之后正常。
remuxing xx.mp4 --> xx.ffm --> xx.flv
missing picture in access unit with size
no frames
unspecified size
ffmpeg xx.mp4 --> xx.ffm --> xx.flv也报错。
ffplay xx.ffm 只有声音,并且也是no frames
ffmpeg xx.mp4 --> xx.flv 正常
remuxing xx.mp4 --> xx.flv 也正常
能不能绕开ffm呢?
像这样
<Stream file.flv>
Format flv
File xx.flv
</Stream>
没有任何报错,可以正常播放。
能不能扩展到实时流呢?
模拟读文件,对于一个新的请求,
要给它发文件头,所以需要缓存文件头。
然后请求包,由于循环缓冲区等原因,会丢包。
remuxing 里面丢几个包试试。
丢前100个包, 可正常播放。
每30个包丢一个,ffplay 没有报错,在那个时间点部分马赛克,其他时间正常播放。
假设用例是这样的:
ffmpeg -i input http://localhost:8090/stream1.flv #只发数据
ffplay http://serverip:8090/stream1.flv #只收数据
ffmpeg为什么要走http?
目的是让ffserver统一管理socket io。
让ffmpeg原封不动的传包, ffserver收到后放到循环缓冲里面。
如果写文件的话,要设计一个ffm类似的文件格式,走老路上了。
收发的文件名一样?
以前不同是因为feed-stream设计是多对一的关系, ffserver要做封包的工作。
现在我们目前只需要一对一的关系,不用重新封包了。
发数据这块,用另外的端口,unix domain socket更好些,但是怎么让
ffmpeg原生地支持:
unix.c
unix://localhost:port/stream1.flv -timeout 1000 -type datagram
为了让ffserver知道数据大小?
到avio http这一层已经没有包的边界了。
如果在fferver端这样:保留http过来的前面几KB数据作为,剩下的循环缓存。
很可能会导致ffplay卡顿。
换个角度,我们把remuxing的read_frame,write_frame放到不同进程。
甚至让文件头信息也共享,这样就没有边界问题了,且不用转换和传输。
具体做法:
把remuxing.c复制两份,使用共享内存,建立一个固定的循环缓冲。
写端只管写,不用管读端;
读端读指针不能超过写指针。
#define PKT_BUF_SIZE 32
typedef struct{
AVFormatContext *ifmt_ctx;
AVFormatContext *ofmt_ctx;
AVPacket *pkt_buf[PKT_BUF_SIZE];
int wpos, rpos;
}share_data_t;
注意我们里面的很多指针也要在共享空间。
但是怎么 malloc from shared mem?
还有一种方案:
不要write_frame,而是avio_write(pkt);
定义一种所有AVFormat的超级格式.sff:
SFFx size data
x = 0表示循环写位置等元数据信息。
x = 1表示是write_header的内容,size 4个字节是后面data的长度。
x = 2表示后面的数据是AVPacket。
"SFF"本身是同步前缀码。
ffserver接收包要缓存,收到完整的包再输出。
因为ffserver里面读到开始的4096字节就read_header,以为codec就全部找到了。
实际上,从先前的demuxing可以看出,还需要读20个包, find_stream_info才能全部找到。
究竟多少个包才够找全呢?
重新设计配置文件。
先剪掉rtp, 以后再加进来。
Feed也不要
凡是关于codec opt都不要。
现在配置文件也不需要了。
剩下的需增加两个功能点:
o POST进来的文件名要记录,用它来关联以后的GET的连接。
o GET连接要维护自己的读指针。
o POST连接也有自己的写指针。
部署问题:
o 已经去掉了不需要的,ffserver在我的64bit ubuntu上仍然有4.4MB,
how to make ffmpeg small ?
o http输出不能chunked
设置输入参数:
av_dict_set(&stream->in_opts, "mpeg2ts_compute_pcr", "1", 0);
avformat_open_input(&infile, stream->feed_filename, stream->ifmt, &stream->in_opts)
设置输出参数:
AVDictionary *opts = NULL;
av_dict_set(&of->opts, "chunked_post", "0", 0);
avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, NULL, &of->opts)
if(s->pb)avio_close(s->pb);
av_dict_free(&of->opts);
o remuxing 异常退出时没有看到ffserver关连接,里面的连接还在。
ffplay ctrl c 中断有, 那说明remuxing没有处理好。
我们处理办法是截获中断信号,avio时会主动查询到会返回AVERROR_EXIT。
其实发现remuxing正常退出,ffserver也没有断开连接,参考原始代码,
把接收长度为零就认为是断开的检查加上后,CLOSE_WAIT就变成正常的TIME_WAIT了。
这种方式,remuxing不用任何改动,就可以处理正常退出和中断退出两种情形。
但是这里有个问题是,remuxing在某种情形下就是不发数据,该怎么兼顾呢?
如果ffserver主动断开ffplay的GET连接,发现ffplay并没有退出,ffplay到ffserver的链路处于CLOSE_WAIT状态。
if(len <= 0)改成if(len < 0) ?
因为POST连接要占用缓存内存也不不小,还是主动断开更好些。
或者heart_beat/timeout/retry_times也可以考虑。
o remuxing 内存占用估计
每次处理一个包都要open_dyn_buf,之后释放掉,能不能只用一个最大的buf呢?
全局avio_open_dyn_buf/avio_close_dyn_buf一次。
每次处理包时,url_resetbuf/存取内部指针。
目前暂不优化,以后如果遇到内存不够时可以考虑这个方案。
/*
Remux streams from one container format to another.
*/
#include
#include
#include
int sff_write_block(AVIOContext *pb, int type, uint8_t *data, uint32_t size);
int sff_write_header(AVFormatContext *ctx);
int sff_write_packet(AVFormatContext *ctx, AVPacket *pkt);
int sff_write_block(AVIOContext *pb, int type, uint8_t *data, uint32_t size)
{
uint8_t tmp[8];
//printf("size %u\t", size);
type = type % 10;
sprintf(tmp, "SFF%d", type);
avio_write(pb, tmp, 4);
avio_wb32(pb, size);
avio_write(pb, data, size);
return 0;
}
int sff_write_header(AVFormatContext *ctx)
{
uint32_t size = 0;
uint8_t *buf = NULL;
int ret = -1;
AVIOContext *pb = NULL, *old = ctx->pb;
if(!ctx ){
return -1;
}
if(avio_open_dyn_buf(&pb) < 0){
return -1;
}
ctx->pb = pb;
if(avformat_write_header(ctx, NULL) < 0){
return -1;
}
size = avio_close_dyn_buf(ctx->pb, &buf);
ctx->pb = old;
ret = sff_write_block(ctx->pb, 1, buf, size);
av_freep(&buf);
return ret;
}
int sff_write_packet(AVFormatContext *ctx, AVPacket *pkt)
{
AVIOContext *pb = NULL, *old = NULL;
uint8_t *buf = NULL;
int size = 0, frame_size = -1, ret = -1;
static int seq = 0;
++seq;
if(!ctx || !ctx->pb)return 0;
if(avio_open_dyn_buf(&pb) < 0){
return -1;
}
pb->seekable = 0;
old = ctx->pb;
ctx->pb = pb;
/*add more pkt info if needed, here stream_index only for illustration.*/
if(pkt){
avio_wb32(pb, pkt->stream_index);
ret = av_interleaved_write_frame(ctx, pkt);
}else{
avio_wb32(pb, (uint32_t)-1);
ret = av_write_trailer(ctx);
}
size = avio_close_dyn_buf(pb, &buf);
frame_size = size - 4;
ctx->pb = old;
if(frame_size <= 0){
//printf("write frame fail err %x %d:%d\n", ret, seq, frame_size);
goto fail;/*its ok*/
}
if(ret < 0){
printf("write frame fail err %x %d:%d\n", ret, seq, frame_size);
goto fail;
}
ret = sff_write_block(ctx->pb, 2, buf, size);
fail:
av_freep(&buf);
return ret;
}
#define M 4
static int64_t start_time[M], curr_time[M];
static int64_t first_dts[M] = {0};
static int64_t curr_dts[M] = {AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE};
int main(int argc, char **argv)
{
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVDictionary *out_opts = NULL;
AVPacket pkt;
const char *fmt, *in_filename, *out_filename;
int ret, i, cmp;
int cnt = 0;
if (argc < 4) {
printf("usage: remuxing input output fmt\n");
return 1;
}
in_filename = argv[1];
out_filename = argv[2];
fmt = argv[3];
av_register_all();
avformat_network_init();
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '%s'", in_filename);
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
av_dump_format(ifmt_ctx, 0, in_filename, 0);
avformat_alloc_output_context2(&ofmt_ctx, NULL, fmt, out_filename);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0) {
fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
av_dump_format(ofmt_ctx, 0, out_filename, 1);
if (!(ofmt->flags & AVFMT_NOFILE)) {
av_dict_set(&out_opts, "chunked_post", "0", 0);
ret = avio_open2(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE, NULL, &out_opts);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'", out_filename);
goto end;
}
}
ret = sff_write_header(ofmt_ctx); //avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
avio_flush(ofmt_ctx->pb);
while (1) {
AVStream *in_stream, *out_stream;
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
i = pkt.stream_index;
if(curr_dts[i] == AV_NOPTS_VALUE && pkt.dts != AV_NOPTS_VALUE){
first_dts[i] = pkt.dts;
start_time[i] = av_gettime_relative();
}
if(pkt.dts != AV_NOPTS_VALUE){
curr_dts[i] = pkt.dts; //us
}
/* copy packet */
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
ret = sff_write_packet(ofmt_ctx, &pkt); //av_interleaved_write_frame(ofmt_ctx, &pkt);
avio_flush(ofmt_ctx->pb);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_free_packet(&pkt);
++cnt;
//printf("cnt %d\t", cnt);
do{
curr_time[i] = av_gettime_relative();
cmp = av_compare_ts(curr_dts[i] - first_dts[i], in_stream->time_base,
curr_time[i] - start_time[i], AV_TIME_BASE_Q);
if(cmp <= 0)break;
av_usleep(10000);
}while(cmp > 0);
}
sff_write_packet(ofmt_ctx, NULL); //av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE) && ofmt_ctx->pb){
avio_close(ofmt_ctx->pb);
av_dict_free(&out_opts);
}
avformat_free_context(ofmt_ctx);
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
printf("end of remux\n");
return 0;
}