bytedeco / javacv

Java interface to OpenCV, FFmpeg, and more
Other
7.59k stars 1.59k forks source link

FFmpegFrameRecorder ,how can i crop? #827

Closed tristin2024 closed 7 years ago

saudet commented 7 years ago

You could use FFmpegFrameFilter for that.

tristin2024 commented 7 years ago

how to use FFmpegFrameFilter ?for example

saudet commented 7 years ago

There are a couple of examples here: https://github.com/bytedeco/javacv/search?utf8=✓&q=FFmpegFrameFilter&type=

tristin2024 commented 7 years ago

String crop = String.format("crop=%d:%d:%d:%d", 240, 320, 0, 0); I add Filter but it is not my want ! image

tristin2024 commented 7 years ago

i want 640*480 -->240:320 height bigger width

saudet commented 7 years ago

You'll need to set that resolution on the FFmpegFrameRecorder.

tristin2024 commented 7 years ago

i have do it,but not work

saudet commented 7 years ago

Please show me what you are doing.

tristin2024 commented 7 years ago

OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(deviceIndex); grabber.setImageMode(FrameGrabber.ImageMode.RAW); grabber.setImageWidth(768); grabber.setImageHeight(1024); 869e6ef8088d7b1142c676e6960c74db

picture minified 13959f00e3746a73913725d2a59e05c5

picture right

tristin2024 commented 7 years ago

picure right use obs

saudet commented 7 years ago

I mean, you're not using FFmpegFrameRecorder at all?

tristin2024 commented 7 years ago
package com.doll.api.core.pushstream;

import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacv.*;

import javax.sound.sampled.*;
import javax.swing.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by xlt on 2017/11/3.
 */
public class PushStreamThread extends Thread {
    final private static int WEBCAM_DEVICE_INDEX = 0;
    final private static int AUDIO_DEVICE_INDEX = 4;
    final private static int FRAME_RATE = 30;
    public static ConcurrentHashMap<Integer, Integer> deviceStatus;
    private long startTime = 0;
    private long videoTS = 0;
    private byte[] oldAudioBytes;
    private byte[] audioBytes;
    private int firstBytesRead;
    private int deviceIndex;

    public PushStreamThread(int deviceIndex) {
        this.deviceIndex = deviceIndex;
    }

    static {
        deviceStatus = new ConcurrentHashMap<>();
    }

    public static int getStreamStatusByIndex(int index) {
        Iterator<Integer> iterator = deviceStatus.keySet().iterator();
        int i = 0;
        while (iterator.hasNext()) {
            Integer key = iterator.next();
            if (i == index) {
                return deviceStatus.get(key);
            }
            i++;
        }
        return 0;
    }

    @Override
    public void run() {
        int captureWidth = 720;
        int captureHeight = 1080;

        /**
         * FrameGrabber 类包含:OpenCVFrameGrabber
         * (opencv_videoio),C1394FrameGrabber, FlyCaptureFrameGrabber,
         * OpenKinectFrameGrabber,PS3EyeFrameGrabber,VideoInputFrameGrabber, 和
         * FFmpegFrameGrabber.
         */
        OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(deviceIndex);
        grabber.setImageMode(FrameGrabber.ImageMode.RAW);
        grabber.setImageWidth(captureWidth);
        grabber.setImageHeight(captureHeight);
        System.out.println("开始抓取摄像头...");
        int isTrue = 0;// 摄像头开启状态
        try {
            grabber.start();
            isTrue += 1;
        } catch (org.bytedeco.javacv.FrameGrabber.Exception e2) {
            if (grabber != null) {
                try {
                    grabber.restart();
                    isTrue += 1;
                } catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
                    isTrue -= 1;
                    try {
                        grabber.stop();
                    } catch (org.bytedeco.javacv.FrameGrabber.Exception e1) {
                        isTrue -= 1;
                    }
                }
            }
        }
        if (isTrue < 0) {
            System.err.println("摄像头首次开启失败,尝试重启也失败!");
            return;
        } else if (isTrue < 1) {
            System.err.println("摄像头开启失败!");
            return;
        } else if (isTrue == 1) {
            System.err.println("摄像头开启成功!");
            deviceStatus.put(deviceIndex, 1);
        } else if (isTrue == 1) {
            deviceStatus.put(deviceIndex, 1);
            System.err.println("摄像头首次开启失败,重新启动成功!");
        }

        /**
         * FFmpegFrameRecorder(String filename, int imageWidth, int imageHeight,
         * int audioChannels) fileName可以是本地文件(会自动创建),也可以是RTMP路径(发布到流媒体服务器)
         * imageWidth = width (为捕获器设置宽) imageHeight = height (为捕获器设置高)
         * audioChannels = 2(立体声);1(单声道);0(无音频)
         */
        final FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("rtmp://video-center.alivecdn.com/wawa-1-2/StreamName?vhost=wawa.shanren.wang", captureWidth, captureHeight, 2);
        recorder.setInterleaved(true);
        /**
         * 该参数用于降低延迟 参考FFMPEG官方文档:https://trac.ffmpeg.org/wiki/StreamingGuide
         * 官方原文参考:ffmpeg -f dshow -i video="Virtual-Camera" -vcodec libx264
         * -tune zerolatency -b 900k -f mpegts udp://10.1.0.102:1234
         */

        recorder.setVideoOption("tune", "zerolatency");
        /**
         * 权衡quality(视频质量)和encode speed(编码速度) values(值):
         * ultrafast(终极快),superfast(超级快), veryfast(非常快), faster(很快), fast(快),
         * medium(中等), slow(慢), slower(很慢), veryslow(非常慢)
         * ultrafast(终极快)提供最少的压缩(低编码器CPU)和最大的视频流大小;而veryslow(非常慢)提供最佳的压缩(高编码器CPU)的同时降低视频流的大小
         * 参考:https://trac.ffmpeg.org/wiki/Encode/H.264 官方原文参考:-preset ultrafast
         * as the name implies provides for the fastest possible encoding. If
         * some tradeoff between quality and encode speed, go for the speed.
         * This might be needed if you are going to be transcoding multiple
         * streams on one machine.
         */
        recorder.setVideoOption("preset", "ultrafast");

        /**
         * 参考转流命令: ffmpeg
         * -i'udp://localhost:5000?fifo_size=1000000&overrun_nonfatal=1' -crf 30
         * -preset ultrafast -acodec aac -strict experimental -ar 44100 -ac
         * 2-b:a 96k -vcodec libx264 -r 25 -b:v 500k -f flv 'rtmp://<wowza
         * serverIP>/live/cam0' -crf 30
         * -设置内容速率因子,这是一个x264的动态比特率参数,它能够在复杂场景下(使用不同比特率,即可变比特率)保持视频质量;
         * 可以设置更低的质量(quality)和比特率(bit rate),参考Encode/H.264 -preset ultrafast
         * -参考上面preset参数,与视频压缩率(视频大小)和速度有关,需要根据情况平衡两大点:压缩率(视频大小),编/解码速度 -acodec
         * aac -设置音频编/解码器 (内部AAC编码) -strict experimental
         * -允许使用一些实验的编解码器(比如上面的内部AAC属于实验编解码器) -ar 44100 设置音频采样率(audio sample
         * rate) -ac 2 指定双通道音频(即立体声) -b:a 96k 设置音频比特率(bit rate) -vcodec libx264
         * 设置视频编解码器(codec) -r 25 -设置帧率(frame rate) -b:v 500k -设置视频比特率(bit
         * rate),比特率越高视频越清晰,视频体积也会变大,需要根据实际选择合理范围 -f flv
         * -提供输出流封装格式(rtmp协议只支持flv封装格式) 'rtmp://<FMS server
         * IP>/live/cam0'-流媒体服务器地址
         */
        recorder.setVideoOption("crf", "28");
        // 2000 kb/s, 720P视频的合理比特率范围
        recorder.setVideoBitrate(2000000);
        // h264编/解码器
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        // 封装格式flv
        recorder.setFormat("flv");
        // 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏)
        recorder.setFrameRate(FRAME_RATE);
        // 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
        recorder.setGopSize(FRAME_RATE * 2);
        // 不可变(固定)音频比特率
        recorder.setAudioOption("crf", "0");
        // 最高质量
        recorder.setAudioQuality(0);
        // 音频比特率
        recorder.setAudioBitrate(192000);
        // 音频采样率
        recorder.setSampleRate(44100);
        // 双通道(立体声)
        recorder.setAudioChannels(2);
        // 音频编/解码器
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
        System.out.println("开始录制...");

        try {
            recorder.start();
        } catch (FrameRecorder.Exception e2) {
            deviceStatus.put(deviceIndex, 0);
            if (recorder != null) {
                System.out.println("关闭失败,尝试重启");
                try {
                    recorder.stop();
                    recorder.start();
                } catch (FrameRecorder.Exception e) {
                    try {
                        System.out.println("开启失败,关闭录制");
                        recorder.stop();
                        return;
                    } catch (FrameRecorder.Exception e1) {
                        return;
                    }
                }
            }

        }
        // 音频捕获
        Thread audioThread = new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                 * 设置音频编码器 最好是系统支持的格式,否则getLine() 会发生错误
                 * 采样率:44.1k;采样率位数:16位;立体声(stereo);是否签名;true:
                 * big-endian字节顺序,false:little-endian字节顺序(详见:ByteOrder类)
                 */
                AudioFormat audioFormat = new AudioFormat(44100.0F, 16, 2, true, false);

                // 通过AudioSystem获取本地音频混合器信息
                Mixer.Info[] minfoSet = AudioSystem.getMixerInfo();
                // 通过AudioSystem获取本地音频混合器
                Mixer mixer = AudioSystem.getMixer(minfoSet[AUDIO_DEVICE_INDEX]);
                // 通过设置好的音频编解码器获取数据线信息
                DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
                try {
                    // 打开并开始捕获音频
                    // 通过line可以获得更多控制权
                    // 获取设备:TargetDataLine line
                    // =(TargetDataLine)mixer.getLine(dataLineInfo);
                    final TargetDataLine line = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
                    line.open(audioFormat);
                    line.start();
                    // 获得当前音频采样率
                    final int sampleRate = (int) audioFormat.getSampleRate();
                    // 获取当前音频通道数量
                    final int numChannels = audioFormat.getChannels();
                    // 初始化音频缓冲区(size是音频采样率*通道数)
                    int audioBufferSize = sampleRate * numChannels;
                    audioBytes = new byte[audioBufferSize];
                    oldAudioBytes = new byte[audioBufferSize];
                    int processors = Runtime.getRuntime().availableProcessors();
                    final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(Math.min(8, processors));
                    exec.scheduleAtFixedRate(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                // 非阻塞方式读取
                                int nBytesRead = line.read(audioBytes, 0, line.available());
                                //System.out.println("sBuff:" + ";nBytesRead=" + nBytesRead);
                                // 因为我们设置的是16位音频格式,所以需要将byte[]转成short[]
                                if (firstBytesRead == 0) {
                                    System.arraycopy(audioBytes, 0, oldAudioBytes, 0, audioBytes.length);
                                    firstBytesRead = nBytesRead;
                                }
                                if (nBytesRead <= 0) {
                                    System.arraycopy(oldAudioBytes, 0, audioBytes, 0, oldAudioBytes.length);
                                    nBytesRead = firstBytesRead;
                                }
                                int nSamplesRead = nBytesRead / 2;
                                short[] samples = new short[nSamplesRead];
                                /**
                                 * ByteBuffer.wrap(audioBytes)-将byte[]数组包装到缓冲区
                                 * ByteBuffer.order(ByteOrder)-按little-endian修改字节顺序,解码器定义的
                                 * ByteBuffer.asShortBuffer()-创建一个新的short[]缓冲区
                                 * ShortBuffer.get(samples)-将缓冲区里short数据传输到short[]
                                 */
                                ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
                                // 将short[]包装到ShortBuffer
                                //System.out.println("sBuff:" + samples.length + ";nSamplesRead=" + nSamplesRead);
                                ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, nSamplesRead);
                                // 按通道录制shortBuffer
                                recorder.recordSamples(sampleRate, numChannels, sBuff);
                                //System.out.println("sBuff:" + sBuff.toString());
                            } catch (FrameRecorder.Exception e) {
                                System.out.println("-----------音频捕捉异常!------------------");
                                deviceStatus.put(deviceIndex, 0);
                                e.printStackTrace();
                                line.close();
                                exec.shutdownNow();
                            }
                        }
                    }, 0, (long) 1000 / FRAME_RATE, TimeUnit.MILLISECONDS);
                } catch (LineUnavailableException e1) {
                    deviceStatus.put(deviceIndex, 0);
                    e1.printStackTrace();
                }
            }
        });
        audioThread.start();

        // javaCV提供了优化非常好的硬件加速组件来帮助显示我们抓取的摄像头视频
        CanvasFrame cFrame = new CanvasFrame("Capture Preview", CanvasFrame.getDefaultGamma() / grabber.getGamma());
        cFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        cFrame.setAlwaysOnTop(true);
        Frame rotatedFrame;
        // 执行抓取(capture)过程
        try {
            while (grabber.grab() != null) {
                rotatedFrame = grabber.grab();
                if (cFrame.isVisible()) {
                    //本机预览要发送的帧
                    cFrame.showImage(rotatedFrame);
                }
                //定义我们的开始时间,当开始时需要先初始化时间戳
                //System.out.println("sBuff:startTime->" + startTime);
                if (startTime == 0)
                    startTime = System.currentTimeMillis();

                // 创建一个 timestamp用来写入帧中
                videoTS = 1000 * (System.currentTimeMillis() - startTime);
                //System.out.println("sBuff:startTime->" + startTime + ";videoTS:" + videoTS + ";timestamp:" + recorder.getTimestamp());
                //检查偏移量
                if (videoTS > recorder.getTimestamp()) {
//                    System.out.println("Lip-flap correction: " + videoTS + " : " + recorder.getTimestamp() + " -> "
//                            + (videoTS - recorder.getTimestamp()));
                    //告诉录制器写入这个timestamp
                    recorder.setTimestamp(videoTS);
                }
                // 发送帧
                try {
                    recorder.record(rotatedFrame);
                } catch (FrameRecorder.Exception e) {
                    e.printStackTrace();
                    System.out.println("录制帧发生异常---------,什么都不做");
                    deviceStatus.put(deviceIndex, 0);
                    break;
                }
            }

        } catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
            deviceStatus.put(deviceIndex, 0);
            e.printStackTrace();
            System.out.println("FrameGrabber.Exception:" + e.getMessage());
            audioThread.interrupt();
        }
        //cFrame.dispose();
        try {
            if (recorder != null) {
                recorder.stop();
            }
        } catch (FrameRecorder.Exception e) {
            System.out.println("关闭录制器失败");
            try {
                if (recorder != null) {
                    grabber.stop();
                }
            } catch (org.bytedeco.javacv.FrameGrabber.Exception e1) {
                System.out.println("关闭摄像头失败");
                return;
            }
        }
        try {
            if (recorder != null) {
                grabber.stop();
            }
        } catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
            System.out.println("关闭摄像头失败");
        }
        new PushStreamThread(deviceIndex).start();
    }
}
saudet commented 7 years ago

So, you're not using FFmpegFrameFilter?

githublms commented 3 years ago

请问,合并两个视频的方式是怎样实现的,我没有找到对应的api调用

githublms commented 3 years ago

请告知,谢谢

jetamie commented 1 year ago

请问,合并两个视频的方式是怎样实现的,我没有找到对应的api调用

有demo的