JavaCV 拉流存储到本地

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;

public class LivePuller {

    public void pull(String rtmp, String outputVideoPath) {
        FFmpegFrameGrabber grabber = null;
        FFmpegFrameRecorder recorder = null;
        try {
            FFmpegLogCallback.set();
            boolean hasStream = false;
            grabber = new FFmpegFrameGrabber(rtmp);
            grabber.setOption("nobuffer", "1");
            // 采集流超时时间,停止推流后,grab() 大致会在 20 秒左右超时停止;值也不宜太小,否则可能有些音视频帧采集不到影响推流
            grabber.setOption("rw_timeout", "1000000");
            grabber.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4);
            // setPixelFormat,可能会导致图片帧保存的是黑白照
//            grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
            grabber.setFormat("flv");
            grabber.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            // 先执行代码后有直播流的情况下,grabber.start() 会抛出异常;因此循环start(),直到有流数据时成功
            while (!hasStream) {
                try {
                    grabber.start();
                    hasStream = true;
                } catch (Exception ignored) {
                } finally {
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            // 重置分辨率
            int imageWidth = grabber.getImageWidth();
            int imageHeight = grabber.getImageHeight();
            if (imageWidth > 1920 || imageHeight > 1080) {
                double wScale = imageWidth * 1.0 / 1920;
                double hScale = imageHeight * 1.0 / 1080;
                double scale = Math.max(wScale, hScale);
                grabber.setImageWidth((int) (imageWidth / scale));
                grabber.setImageHeight((int) (imageHeight / scale));
            }
            recorder = new FFmpegFrameRecorder(outputVideoPath, grabber.getImageWidth(), grabber.getImageHeight());
            recorder.setOption("nobuffer", "1");
            recorder.setAudioChannels(grabber.getAudioChannels());
            recorder.setFrameRate(grabber.getFrameRate());  // 保持和采集一样的帧率,避免音画不同步;
            recorder.setVideoQuality(0);    // 视频质量(清晰度)
            recorder.setAudioQuality(0);    // 音频质量

            // --------------------------- 非必须 ------------------------------
            // 当设置了下方的录制器的视频编码,需要再加上 setVideoBitrate 保证视频质量
            recorder.setVideoBitrate(grabber.getVideoBitrate());
            recorder.setVideoCodec(grabber.getVideoCodec());    // 指定为 mp4 录制时需要 avcodec.AV_CODEC_ID_MPEG4
//            // 当上面 grabber 没有设置 setPixelFormat,recorder.setPixelFormat 最好不要使用 grabber.getPixelFormat();可能异常
//            recorder.setPixelFormat(grabber.getPixelFormat());
//            recorder.setFormat(grabber.getFormat());
//            recorder.setAudioCodec(grabber.getAudioCodec());
//            recorder.setSampleRate(grabber.getSampleRate());
            recorder.start();

            Frame frame;
            while ((frame = grabber.grab()) != null) {
                // 此处不能判断 frame.streamIndex 为 avutil.AVMEDIA_TYPE_VIDEO 或 avutil.AVMEDIA_TYPE_DATA;遇到过采集流时两个类型是反着的
                if (frame.image != null || frame.samples != null) {
                    // frame.image 是图像数据,frame.samples 是声音数据
                    recorder.record(frame);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (grabber != null) {
                try {
                    grabber.close();
                } catch (FrameGrabber.Exception e) {
                    e.printStackTrace();
                }
            }
            if (recorder != null) {
                try {
                    recorder.close();
                } catch (FrameRecorder.Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

  拉流整体代码如上,基本该有的地方都有注释;同 JavaCV 推流 也是改变分辨率等参数保证拉流速率,以及设置帧率保证音画同步;另外上面提到 grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); 不要设置,避免后续的 JavaCV 保存视频帧图片 时保存的图片是黑白的;另外上面的代码经过测试是可以将拉取的音视频流保存成 mp4 格式,通过浏览器直接播放的

  注意测试 mp4 文件保存的时候,尽量每次写入一个新的文件;避免在调试过程中,出现因浏览器缓存等问题导致之前有问题的 mp4 的文件,在参数调整好后依然不能正常播放等情况

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

推荐阅读更多精彩内容