Javacv rtmp录制视频报错记录

接到任务需要把摄像头视频流保存到本地MP4,javacv中整合了ffmpeg和opencv的API,可以在java中很方便的调用。javacv版本为1.5.7,按照资料代码逻辑很简单,如下:

FFmpegLogCallback.set();
String url = "rtmp://10.0.24.23/live/3001";
//创建一个拉流器,url可以是音视频文件或流媒体地址
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(url);
grabber.start();

String localUrl = "/tmp/javacv.mp4";
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(localUrl, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.start();

LocalDateTime startTime = LocalDateTime.now();
Frame frame;
while (startTime.plusSeconds(200).compareTo(LocalDateTime.now()) > 0 
&& (frame = grabber.grab()) != null) {
    recorder.record(frame);
}
recorder.stop();
grabber.stop();

测试视频流是文件、rtsp时都可以正常录制,但使用rtmp时出现了报错:

Caused by: org.bytedeco.javacv.FFmpegFrameRecorder$Exception: avcodec_send_frame() error -541478725: Error sending a video frame for encoding. (For more details, make sure FFmpegLogCallback.set() has been called.)
    at org.bytedeco.javacv.FFmpegFrameRecorder.recordImage(FFmpegFrameRecorder.java:1056) ~[javacv-1.5.7.jar:1.5.7]
    at org.bytedeco.javacv.FFmpegFrameRecorder.record(FFmpegFrameRecorder.java:961) ~[javacv-1.5.7.jar:1.5.7]
    at org.bytedeco.javacv.FFmpegFrameRecorder.record(FFmpegFrameRecorder.java:954) ~[javacv-1.5.7.jar:1.5.7]
    at cn.sibat.cvtest.MainThread.run(MainThread.java:70) ~[classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771) [spring-boot-2.7.2.jar:2.7.2]
    ... 5 common frames omitted

实际出错的源码位置:

public synchronized boolean recordImage(int width, int height, int depth, int channels, int stride, int pixelFormat, Buffer ... image) throws Exception {
    try (PointerScope scope = new PointerScope()) {

...省略...
        /* encode the image */
        picture.quality(video_c.global_quality());
        /* 此处判断了avcodec_send_frame 函数返回小于0则直接抛出异常 */
        if ((ret = avcodec_send_frame(video_c, image == null || image.length == 0 ? null : picture)) < 0
                && image != null && image.length != 0) {
            throw new Exception("avcodec_send_frame() error " + ret + ": Error sending a video frame for encoding.");
        }
        picture.pts(picture.pts() + 1); // magic required by libx264
...省略...
}

avcodec_send_frame() 是ffmpeg的视频编码函数,函数返回-541478725(AVERROR_EOF)的意思是检测到了文件结束,但实际流并没有结束。此处javacv判断我们要在一个已经结束的流中进行编码,于是直接抛出了异常。开始以为是我本地rtmp推流的问题,但是换了一个在网上找的开放的rtmp流(伊拉克 Al Sharqiya 电视台:rtmp://ns8.indexforce.com/home/mystream)问题依旧。简单记录下排查流程。

1. 在github上,提交个issues

issues地址:https://github.com/bytedeco/javacv/issues/1858

javacv作者响应很快,但并没有明确定位到问题。详细信息可以去issues中查看,后续也可能会更新。

2. 尝试降低版本

V1.5.7 --> V1.5.6:没有变化

V1.5.7 --> V1.5.4:出现了新的异常信息:avcodec_encode_video2() error -542398533: Could not encode video packet.

avcodec_encode_video2()是ffmpeg 3.1版本之前用于视频编码的函数,3.1版本以后替换成了avcodec_send_frame()

V1.5.7 --> V1.5.5:没有报错,录制成功!

根据1.5.4版本的报错,发现一个非常类似的情况:https://github.com/bytedeco/javacv/issues/1563

该BUG在1.5.5修复了,猜测这就是1.5.5版本可以成功的原因。但是1.5.6开始,引用的ffmpeg中编码函数改为avcodec_send_frame(),重新引入了1.5.4中类似的问题。

3. 代码优化

虽然1.5.5可以暂时实现功能,但没有找到具体问题点,心里不踏实,所以花了2天时间,在代码继续做一些debug,试图找到什么突破。

while (startTime.plusSeconds(200).compareTo(LocalDateTime.now()) > 0 
&& (frame = grabber.grab()) != null) {
    System.out.println(frame.getTypes());
    recorder.record(frame);
}

[DATA]
[AUDIO]
[VIDEO]
[AUDIO]
[VIDEO]
[AUDIO]
...

将每次拉流后,获取的frame类型打印出来,发现只有第一帧是[DATA]类型,其他帧是[VIDEO]或[AUDIO]类型。

当把第一帧忽略掉后,视频就可以正常录制了!最终代码:

FFmpegLogCallback.set();
String url = "rtmp://10.0.24.23/live/3001";
//创建一个拉流器,url可以是音视频文件或流媒体地址
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(url);
grabber.start();

String localUrl = "/tmp/javacv.mp4";
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(localUrl, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.start();

LocalDateTime startTime = LocalDateTime.now();
Frame frame = grabber.grab();
System.out.println("忽略掉第一帧,类型为:" + frame.getTypes());
while (startTime.plusSeconds(200).compareTo(LocalDateTime.now()) > 0 
&& (frame = grabber.grab()) != null) {
    recorder.record(frame);
}
recorder.stop();
grabber.stop();

由于不了解RTMP协议原理,不清楚第一帧在这个过程中起到的作用,猜测可能就是javacv在处理这一“DATA”帧时出现了判断错误。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容