h5 video 标签播放经过 java 使用 ws.schild( jave、ffmpeg ) 压缩后的 mp4 视频只有声音无画面的问题排查记录

  1. 引入 ws.schild MAVEN 依赖:
XML 复制代码
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-all-deps</artifactId>
            <version>3.5.0</version>
        </dependency>
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>3.5.0</version>
        </dependency>
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-win64</artifactId>
            <version>3.5.0</version>
        </dependency>
  1. MyVideoUtils:
java 复制代码
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;
import ws.schild.jave.info.VideoSize;
import ws.schild.jave.progress.EncoderProgressListener;

import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import java.util.Set;

 
public class MyVideoUtils {
 
public static void main(String[] args) throws EncoderException {
        // 压缩前文件路径
        File source = new File("D:\\素材\\视频\\video_0001.mp4");

        // 压缩后的文件路径
        File target = new File("D:\\素材\\视频\\video_0001_output1.mp4");


        compress( source,target,0.3 );
}
 
   
    public static void compress(File sourceVideoFile, File targetVideoFile, Double scale) throws EncoderException {
        try {
            System.out.println("---------------开始压缩---------------");
            long start = System.currentTimeMillis();

            // 尺寸的比例
            BigDecimal scale_size = BigDecimal.valueOf(scale);

            // 码率相关的比率
            BigDecimal scale_rate = BigDecimal.valueOf(scale)
                                                .multiply(BigDecimal.valueOf(scale));

            // 输出源视频的信息
            MultimediaObject multimediaObject_source = new MultimediaObject(sourceVideoFile);
            MultimediaInfo multimediaInfo_source = multimediaObject_source.getInfo();
            AudioInfo audioInfo_source = multimediaInfo_source.getAudio();
            VideoInfo videoInfo_source = multimediaInfo_source.getVideo();

            // 时长
            long seconds = multimediaInfo_source.getDuration() / 1000L;
            // 每秒几帧
            float frameRate = videoInfo_source.getFrameRate();

            System.out.println( "seconds = " + seconds );
            System.out.println( "frameRate = " + frameRate );
            int totalFrame = BigDecimal.valueOf(seconds).multiply(BigDecimal.valueOf(frameRate)).intValue();
            System.out.println( "totalFrame = " + totalFrame );

            System.out.println( "原视频 bitRate = " + videoInfo_source.getBitRate() );
            System.out.println( "原视频 frameRate = " + videoInfo_source.getFrameRate() );
            System.out.println( "原视频 decoder = " + videoInfo_source.getDecoder() );
            VideoSize videoSize_source = videoInfo_source.getSize();
            System.out.println( "源视频宽x高:" + videoSize_source.getWidth() + "x" + videoSize_source.getHeight() );


            Map<String, String> metadata = videoInfo_source.getMetadata();
            Set<String> keys = metadata.keySet();
            for( String key:keys ){
                System.out.println( key + " = " + metadata.get( key ) );
            }
            System.out.println();

            // 音频编码属性配置
            AudioAttributes audioAttributes = new AudioAttributes();
            audioAttributes.setCodec("libmp3lame");

            int audioBitRate_new = BigDecimal.valueOf(audioInfo_source.getBitRate()).multiply(scale_rate).intValue();
            System.out.println( "audioBitRate_new = " + audioBitRate_new );
            audioAttributes.setBitRate( audioBitRate_new  );
            // 设置重新编码的音频流中使用的声道数(1 =单声道,2 = 双声道(立体声))
            audioAttributes.setChannels(1);

            // int audioSamplingRate_new = BigDecimal.valueOf(audioInfo_source.getSamplingRate()).multiply(scale_rate).intValue();
            // System.out.println( "audioSamplingRate_new = " + audioSamplingRate_new );
            // todo 设置此值报错 "ws.schild.jave.EncoderException: Exit code of ffmpeg encoding run is 1",暂不知道什么原因???
            // audioAttributes.setSamplingRate( audioSamplingRate_new );

            // 视频编码属性配置
            VideoAttributes videoAttributes = new VideoAttributes();
            // 设置编码
            // videoAttributes.setCodec("mpeg4");
            videoAttributes.setCodec( "h264" );

            int videoBitRate_new = BigDecimal.valueOf(videoInfo_source.getBitRate()).multiply(scale_rate).intValue();
            System.out.println( "videoBitRate_new = " + videoBitRate_new );
            videoAttributes.setBitRate( videoBitRate_new );

            int newHeight = BigDecimal.valueOf(videoInfo_source.getSize().getHeight()).multiply(scale_size).intValue();
            int newWidth = BigDecimal.valueOf(videoInfo_source.getSize().getWidth()).multiply(scale_size).intValue();
            //  新的宽高都必须是2的整数倍!!!
            newHeight = MyMathUtils.getClosestNuumberThatCanBeDividedBy2( newHeight );
            newWidth = MyMathUtils.getClosestNuumberThatCanBeDividedBy2( newWidth );
            VideoSize videoSize_new = new VideoSize( newWidth,newHeight );
            videoAttributes.setSize( videoSize_new );

            // 编码设置
            EncodingAttributes encodingAttributes = new EncodingAttributes();
            encodingAttributes.setOutputFormat("mp4");
            encodingAttributes.setAudioAttributes( audioAttributes );
            encodingAttributes.setVideoAttributes( videoAttributes );
            // 设置值编码
            Encoder encoder = new Encoder();
            // encoder.encode( multimediaObject_source, targetVideoFile, encodingAttributes );
            // 压缩转换进度监听
            EncoderProgressListener encoderProgressListener = new EncoderProgressListener() {
                @Override
                public void sourceInfo(MultimediaInfo info) {
                }

                @Override
                public void progress(int permil) {
                    double processPercent = BigDecimal.valueOf(permil)
                                                        .divide(BigDecimal.valueOf(1000d), 4, RoundingMode.HALF_UP)
                                                        .doubleValue();
                    System.out.println( "压缩转换进度:" + MyMathUtils.double2PercentFormat( processPercent ) );
                }

                @Override
                public void message(String message) {
                    System.out.println( "message ------------------------------> " + message );
                }
            };
            encoder.encode( multimediaObject_source, targetVideoFile, encodingAttributes,encoderProgressListener );

            System.out.println("---------------结束压缩---------------");
            long end = System.currentTimeMillis();
            System.out.println("压缩前大小:"+ MyMathUtils.byte2M( sourceVideoFile.length() ) + "M,压缩后大小:" + MyMathUtils.byte2M( targetVideoFile.length() ) + "M" );
            System.out.println("压缩耗时:" + MyMathUtils.mill2Second(  ( end - start ) ) + "秒" );
        } catch (EncoderException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
    }
}
  1. MyMathUtils:
java 复制代码
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

public class MyMathUtils {

    public static Integer getScale(Double num) {
        if( num == null || num == 0d ){
            return 0;
        }
        double decimalPart = BigDecimal.valueOf(num).subtract(BigDecimal.valueOf(num.intValue())).doubleValue();
        if( decimalPart == 0d ){
            return 0;
        }
        String decimalPartStr = String.valueOf(decimalPart);
        String decimalPartStr1 = decimalPartStr.substring( decimalPartStr.indexOf(".") + 1 );
        return decimalPartStr1.length();
    }

    /**
     * 计算 Double 集合中不为空并且大于0的元素的个数
     * @param nums
     * @return
     */
    public static Integer calculateCountForNotNullAndBiggerThanZero(List<Double> nums) {
        if( nums == null || nums.size()== 0 ){
            return 0;
        }
        Integer count = 0;
        for( Double num:nums ){
            if( num != null && num > 0d ){
                count++;
            }
        }
        return count;
    }

    public static Double calculateSum(List<Double> nums) {
        if( nums == null || nums.size() == 0 ){
            return 0d;
        }
        BigDecimal sum = BigDecimal.ZERO;
        for( Double num:nums ){
            if( num == null || num == 0d ){
                continue;
            }
            sum = sum.add( BigDecimal.valueOf( num ) );
        }
        return sum.doubleValue();
    }

    public static Double nullDouble2Zero(Double num) {
        if( num == null ){
            return 0d;
        }
        return num;
    }

    public static Integer nullInteger2Zero(Integer num) {
        if( num == null ){
            return 0;
        }
        return num;
    }

    public static Double float2Double(Float f) {
        if( f == null ){
            return null;
        }
        return f.doubleValue();
    }

    public static String double2PercentFormat(Double d) {
        if( d == null ){
            d = 0d;
        }
        double d_percent = BigDecimal.valueOf(d).multiply(BigDecimal.valueOf(100d)).setScale(4, RoundingMode.HALF_UP).doubleValue();
        return d_percent + "%";
    }

    public static Double byte2M( Long byteLenth ){
        return BigDecimal.valueOf( byteLenth )
                            .divide( BigDecimal.valueOf( 1024d ),4,RoundingMode.HALF_UP )
                            .divide( BigDecimal.valueOf( 1024d ),4,RoundingMode.HALF_UP )
                            .doubleValue();
    }

    public static Double mill2Second( Long mill ){
        return BigDecimal.valueOf( mill )
                .divide( BigDecimal.valueOf( 1000d ),4,RoundingMode.HALF_UP )
                .doubleValue();
    }

    /**
     * 获得与传入的数字 num 最接近的能被2整除的数字
     * @param num
     * @return
     */
    public static int getClosestNuumberThatCanBeDividedBy2(int num) {
        int result = BigDecimal.valueOf(BigDecimal.valueOf(num)
                                                    .divide(BigDecimal.valueOf(2d), 2, RoundingMode.HALF_UP)
                                                    .intValue())
                                .multiply(BigDecimal.valueOf(2d))
                                .intValue();
        return result;
    }
}
  1. 将压缩后的文件上传至文件服务器( 例如 minio )获得在线 url,例如 :https://xxx.xxx.com/minio/xxx-bucket/20240705131522-124765sd65sad65sa6d7asd6sa7d56235e675sadasd.mp4,并使用 ht video 标签播放:
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MP4 Video Player Demo</title>
</head>
<body>

    <video width="320" height="240" controls>
        <source src="https://xxx.xxx.com/minio/xxx-bucket/20240705131522-124765sd65sad65sa6d7asd6sa7d56235e675sadasd.mp4" type="video/mp4">
        您的浏览器不支持Video标签。
    </video>
</body>
</html>

播放效果如下:

只有声音没有画面,但是播放压缩之前的原始视频就ok,使用 vlc查看下视频编码信息:

发现编解码器不一样,于是尝试将编解码器换成H264试一下:

java 复制代码
 // videoAttributes.setCodec("mpeg4");
 videoAttributes.setCodec( "h264" );

重新压缩后播放出画面了:

相关推荐
儿时可乖了几秒前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol1 分钟前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-131418 分钟前
jdk各个版本介绍
java
天天扭码37 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶38 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
简鹿办公38 分钟前
如何提取某站 MV 视频中的音乐为 MP3 音频
音视频·简鹿视频格式转换器·视频提取mp3音频
FIN技术铺42 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
yufengxinpian1 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django