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

时间:2024-07-08 18:47:53

1. 引入  ws.schild MAVEN 依赖:


        <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>

2. MyVideoUtils:



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();
        }
    }
}

3. MyMathUtils:

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;
    }
}

4. 将压缩后的文件上传至文件服务器( 例如 minio )获得在线 url,例如 :https://xxx.xxx.com/minio/xxx-bucket/20240705131522-124765sd65sad65sa6d7asd6sa7d56235e675sadasd.mp4,并使用 ht  video 标签播放:

<!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试一下:

 // videoAttributes.setCodec("mpeg4");
 videoAttributes.setCodec( "h264" );

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