JavaCV本地視頻流通過幀圖片添加文本進行字幕合成


  音視頻的Java框架找了一大圈,除了JavaCV,目前找不到其他的。JavaCV封裝了對底層C的調用,最終實際上執行的都是FFMPEG的函數。現在有個頭疼的問題,FFMPEG的字幕合成用命令行一敲就完事了,比如想往input.mkv里合入字幕subtitles.srt,輸出到output.mkv,簡單的很:

ffmpeg -i input.mkv -vf subtitles=subtitles.srt output.mkv

 

  上面的-vf參數表示內嵌字幕,也就是不用手動在播放器里設置,字幕就自己出來了。JavaCV怎么實現字幕合成呢?找了一大圈,沒有。OpenCV倒是可以實現,就是比較麻煩,大概思路是:音視頻幀 -> 視頻幀 -> 視頻幀轉圖片 -> 字幕文本畫到圖片指定位置上 ->  圖片轉回視頻幀 -> 寫視頻幀 -> 寫音頻幀。說白了就是文本水印。可惜OpenCV不支持中文,畫出來都是問號。

  OpenCV就是一個畫圖工具而已,換,用Java自帶的awt畫圖工具對象Graphics2D。不廢話,上代碼:

  1、pom.xml添加Java CV依賴:

        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.2</version>
        </dependency>

 

  2、示例代碼,這里用幾句話模擬字幕循環輸出,並展示合入字幕的時間:

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;
import org.bytedeco.javacv.Frame;
import sun.font.FontDesignMetrics;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class SubtitleMix {

    private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws FrameGrabber.Exception, FrameRecorder.Exception {
        // 構造測試字幕
        String[] test = {
                "世上無難事",
                "只怕有心人",
                "只要思想不滑坡",
                "辦法總比困難多",
                "長江后浪推前浪",
                "前浪死在沙灘上"
        };

        // 為連續的50幀設置同一個測試字幕文本
        List<String> testStr = new ArrayList<>();
        for (int i = 0; i < 300; i++) {
            testStr.add(test[i / 50]);
        }

        // 設置源視頻、加字幕后的視頻文件路徑
        FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault("E:\\BaiduNetdiskDownload\\testout.mkv");
        grabber.start();
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("E:\\BaiduNetdiskDownload\\outtest.mkv",
                1280, 720, 2);

        // 視頻相關配置,取原視頻配置
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.setVideoCodec(grabber.getVideoCodec());
        recorder.setVideoBitrate(grabber.getVideoBitrate());

        // 音頻相關配置,取原音頻配置
        recorder.setSampleRate(grabber.getSampleRate());
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_MP3);

        recorder.start();
        System.out.println("准備開始推流...");
        Java2DFrameConverter converter = new Java2DFrameConverter();
        Frame frame;
        int i = 0;
        while ((frame = grabber.grab()) != null) {
            // 從視頻幀中獲取圖片
            if (frame.image != null) {

                BufferedImage bufferedImage = converter.getBufferedImage(frame);

                // 對圖片進行文本合入
                bufferedImage = addSubtitle(bufferedImage, testStr.get(i++ % 300));

                // 視頻幀賦值,寫入輸出流
                frame.image = converter.getFrame(bufferedImage).image;
                recorder.record(frame);
            }

            // 音頻幀寫入輸出流
            if(frame.samples != null) {
                recorder.record(frame);
            }
        }
        System.out.println("推流結束...");
        grabber.stop();
        recorder.stop();
    }

    /**
     * 圖片添加文本
     *
     * @param bufImg
     * @param subTitleContent
     * @return
     */
    private static BufferedImage addSubtitle(BufferedImage bufImg, String subTitleContent) {

        // 添加字幕時的時間
        Font font = new Font("微軟雅黑", Font.BOLD, 32);
        String timeContent = sdf.format(new Date());
        FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
        Graphics2D graphics = bufImg.createGraphics();
        graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        //設置圖片背景
        graphics.drawImage(bufImg, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);

        //設置左上方時間顯示
        graphics.setColor(Color.orange);
        graphics.setFont(font);
        graphics.drawString(timeContent, 0, metrics.getAscent());

        // 計算文字長度,計算居中的x點坐標
        int textWidth = metrics.stringWidth(subTitleContent);
        int widthX = (bufImg.getWidth() - textWidth) / 2;
        graphics.setColor(Color.red);
        graphics.setFont(font);
        graphics.drawString(subTitleContent, widthX, bufImg.getHeight() - 100);
        graphics.dispose();
        return bufImg;
    }
}

 

  打完收工,看看效果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM