HarmonyOS实现音视频分离合成和截取 原创 精华

钟洪发老师
发布于 2022-1-20 11:41
浏览
2收藏

音视频分离合理直播分享

权限配置:

"reqPermissions": [
  {"name": "ohos.permission.WRITE_MEDIA"},
  {"name": "ohos.permission.READ_MEDIA"}
]

requestPermissionsFromUser(
                new String[]{SystemPermission.WRITE_MEDIA
                        ,SystemPermission.READ_MEDIA},0);

界面布局:

主页:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Button
        ohos:id="$+id:go_separate_btn"
        ohos:text="音视频分离和合成"
        ohos:width="200vp"
        ohos:height="match_content"
        ohos:padding="8vp"
        ohos:background_element="blue"
        ohos:text_color="#fff"
        ohos:text_size="20fp"/>

    <Button
        ohos:id="$+id:go_cut_btn"
        ohos:text="视频剪切"
        ohos:width="200vp"
        ohos:height="match_content"
        ohos:padding="8vp"
        ohos:background_element="blue"
        ohos:text_color="#fff"
        ohos:top_margin="15vp"
        ohos:text_size="20fp"/>

    <Button
        ohos:id="$+id:go_join_btn"
        ohos:text="视频拼接"
        ohos:width="200vp"
        ohos:height="match_content"
        ohos:padding="8vp"
        ohos:background_element="blue"
        ohos:text_color="#fff"
        ohos:top_margin="15vp"
        ohos:text_size="20fp"/>

</DirectionalLayout>

分离合成SeparateAbility:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="start"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text_size="15fp"
        ohos:text_color="#000"
        ohos:text="提供音频数据:"
        ohos:text_alignment="left"
        />

    <DirectionalLayout
        ohos:id="$+id:play_directionalLayout1"
        ohos:width="match_parent"
        ohos:height="240vp"
        ohos:alignment="center"
        ohos:background_element="#000000"
        />
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:alignment="vertical_center"
        ohos:orientation="horizontal"
        >

        <Text
            ohos:id="$+id:current_time1"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>

        <Slider
            ohos:id="$+id:progress1"
            ohos:height="35vp"
            ohos:width="0vp"
            ohos:start_margin="5vp"
            ohos:end_margin="5vp"
            ohos:orientation="horizontal"
            ohos:progress_element="#FF6103"
            ohos:progress_width="5vp"
            ohos:weight="1"/>

        <Text
            ohos:id="$+id:end_time1"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>
    </DirectionalLayout>

    <DirectionalLayout
        ohos:width="match_parent"
        ohos:height="match_content"
        ohos:top_margin="0vp"
        ohos:left_margin="20vp"
        ohos:right_margin="20vp"
        ohos:layout_alignment="horizontal_center"
        ohos:orientation="horizontal">

        <Button
            ohos:id="$+id:start_time_btn1"
            ohos:text="确定开始时间"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:right_margin="10vp"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>

        <Button
            ohos:id="$+id:end_time_btn1"
            ohos:text="确定结束时间"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:left_margin="10vp"
            ohos:layout_alignment="vertical_center"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>
    </DirectionalLayout>


    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text_size="15fp"
        ohos:text_color="#000"
        ohos:text="提供视频数据:"
        ohos:text_alignment="left"
        ohos:top_margin="10vp"
        />

    <DirectionalLayout
        ohos:id="$+id:play_directionalLayout2"
        ohos:width="match_parent"
        ohos:height="240vp"
        ohos:alignment="center"
        ohos:background_element="#000000"
        />
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:alignment="vertical_center"
        ohos:orientation="horizontal"
        >

        <Text
            ohos:id="$+id:current_time2"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>

        <Slider
            ohos:id="$+id:progress2"
            ohos:height="35vp"
            ohos:width="0vp"
            ohos:start_margin="5vp"
            ohos:end_margin="5vp"
            ohos:orientation="horizontal"
            ohos:progress_element="#FF6103"
            ohos:progress_width="5vp"
            ohos:weight="1"/>

        <Text
            ohos:id="$+id:end_time2"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>
    </DirectionalLayout>

    <DirectionalLayout
        ohos:width="match_parent"
        ohos:height="match_content"
        ohos:top_margin="0vp"
        ohos:left_margin="20vp"
        ohos:right_margin="20vp"
        ohos:layout_alignment="horizontal_center"
        ohos:orientation="horizontal">

        <Button
            ohos:id="$+id:start_time_btn2"
            ohos:text="确定开始时间"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:right_margin="10vp"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>

        <Button
            ohos:id="$+id:end_time_btn2"
            ohos:text="确定结束时间"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:left_margin="10vp"
            ohos:layout_alignment="vertical_center"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>
    </DirectionalLayout>
    <Button
        ohos:id="$+id:separate_btn"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:padding="5vp"
        ohos:text="开始分离合成"
        ohos:text_size="18fp"
        ohos:background_element="#ff0000"
        ohos:top_margin="15vp"
        ohos:layout_alignment="center"
        />
</DirectionalLayout>

视频剪切:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text_size="20fp"
        ohos:text_color="#000"
        ohos:text="原视频"
        ohos:text_alignment="center"
        />

    <DirectionalLayout
        ohos:id="$+id:cut_play_directionalLayout"
        ohos:width="match_parent"
        ohos:height="240vp"
        ohos:alignment="center"
        ohos:background_element="#000000"
        />
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:alignment="vertical_center"
        ohos:orientation="horizontal"
        >

        <Text
            ohos:id="$+id:current_time"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>

        <Slider
            ohos:id="$+id:progress"
            ohos:height="35vp"
            ohos:width="0vp"
            ohos:start_margin="5vp"
            ohos:end_margin="5vp"
            ohos:orientation="horizontal"
            ohos:progress_element="#FF6103"
            ohos:progress_width="5vp"
            ohos:weight="1"/>

        <Text
            ohos:id="$+id:end_time"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>
    </DirectionalLayout>

    <DirectionalLayout
        ohos:width="match_parent"
        ohos:height="match_content"
        ohos:top_margin="0vp"
        ohos:left_margin="20vp"
        ohos:right_margin="20vp"
        ohos:layout_alignment="horizontal_center"
        ohos:orientation="horizontal">

        <Button
            ohos:id="$+id:start_time_btn"
            ohos:text="确定开始时间"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="8vp"
            ohos:right_margin="10vp"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="20fp"/>

        <Button
            ohos:id="$+id:end_time_btn"
            ohos:text="确定结束时间"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="8vp"
            ohos:left_margin="10vp"
            ohos:layout_alignment="vertical_center"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="20fp"/>
    </DirectionalLayout>

    <Button
        ohos:id="$+id:cut_btn"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:padding="8vp"
        ohos:text="开始剪切"
        ohos:text_size="22fp"
        ohos:background_element="#ff0000"
        ohos:top_margin="15vp"
        ohos:layout_alignment="center"
        />

</DirectionalLayout>

工具类:

读写外部存储公共目录工具:

public class StorageFileUtils {
   public enum MediaType{
        VIDEO,
        IMAGE,
        AUDIO,
        DOWNLOADS
    }

    /**
     * 获取媒体文件保存的外部存储公共目录的fd,为了读,查询
     * @param context
     * @param fileName
     * @return
     */
    public static FileDescriptor getPublicFileFdForRead(Context context,MediaType type,String fileName){
        DataAbilityHelper helper = DataAbilityHelper.creator(context); //api7以后,create方法替代了
        Uri externalDataAbilityUri = null;
        switch (type){
            case VIDEO:
                externalDataAbilityUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;
                break;
            case IMAGE:
                externalDataAbilityUri = AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI;
                break;
            case DOWNLOADS:
                externalDataAbilityUri = AVStorage.Downloads.EXTERNAL_DATA_ABILITY_URI;
                break;
            case AUDIO:
                externalDataAbilityUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;
                break;
        }
        ResultSet rs = null;
        FileDescriptor fileDescriptor = null;
        try {
            DataAbilityPredicates predicates = 
                new DataAbilityPredicates("_display_name = '" + fileName + "'");
            rs = helper.query(externalDataAbilityUri,new String[]{AVStorage.Video.Media.ID},predicates);
            System.out.println("rs count:" + rs.getRowCount());
            if(rs != null){
                while (rs.goToNextRow()) {
                    int mediaId = rs.getInt(rs.getColumnIndexForName(AVStorage.Video.Media.ID));
                    Uri uri = Uri.appendEncodedPathToUri(externalDataAbilityUri,"" + mediaId);
                    fileDescriptor = helper.openFile(uri, "r");
                    return fileDescriptor;
                }
            }
        } catch (Exception e) {
            System.out.println("rs read error!");
            e.printStackTrace();
        } finally {
            if (rs != null) {
                rs.close();
            }
        }
        return fileDescriptor;
    }

    /**
     * 获取媒体文件保存的外部存储公共目录的fd,为了写,插入
     * @param context
     * @param type
     * @param name
     * @return
     */
    public static FileDescriptor getPublicFdForInsert(Context context, 
                                                                       MediaType type, String name) {
        DataAbilityHelper helper = DataAbilityHelper.creator(context);
        Uri externalDataAbilityUri = null;
        ValuesBucket vb = new ValuesBucket();
        switch (type){
            case VIDEO:
                externalDataAbilityUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;
                vb.putString(AVStorage.Video.Media.DISPLAY_NAME,name);
                break;
            case IMAGE:
                externalDataAbilityUri = AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI;
                vb.putString(AVStorage.Images.Media.DISPLAY_NAME,name);
                break;
            case DOWNLOADS:
                externalDataAbilityUri = AVStorage.Downloads.EXTERNAL_DATA_ABILITY_URI;
                vb.putString(AVStorage.Downloads.DISPLAY_NAME,name);
                break;
            case AUDIO:
                externalDataAbilityUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;//这里uri对应目录
                vb.putString(AVStorage.Audio.Media.DISPLAY_NAME,name);
                break;
        }
        FileOutputStream outputStream = null;
        FileDescriptor fd = null;
        try {
            int id = helper.insert(externalDataAbilityUri, vb);
            System.out.println("insert rs:" + id);
            Uri uri = Uri.appendEncodedPathToUri(externalDataAbilityUri, ""+id); //这个uri对应文件
            outputStream = (FileOutputStream) helper.obtainOutputStream(uri);
            fd = outputStream.getFD();
            return fd;
        } catch (Exception e) {
            System.out.println(" helper.insert error!");
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return fd;
    }
}

视频处理核心工具类:

public class MediaHandler {
    public static boolean combineTwoVideos(Context context,
                                        FileDescriptor audioFd,
                                        int audioStartTime,
                                        int audioEndTime,
                                        FileDescriptor videoFd,
                                        int videoStartTime,
                                        int videoEndTime,
                                        FileDescriptor outFd) {

        if(audioFd == null || videoFd==null){
            System.out.println("=======audioSrcFd或者framesSrcFd文件没读到!");
            return false;
        }

        Extractor audioExtractor = new Extractor();
        int mainAudioExtractorTrackIndex = -1; //(提供音频的)视频的音频轨道号
        int mainAudioMuxerTrackIndex = -1; //合成后的视频的音频轨
        int mainAudioMaxInputSize = 0; //能获取的音频的最大值

        Extractor frameExtractor = new Extractor();
        int frameExtractorTrackIndex = -1; //(提供画面的)视频的视频轨
        int frameMuxerTrackIndex = -1; //合成后的视频的视频轨
        int frameMaxInputSize = 0; //能获取的视频的最大值
        int frameRate = 0; //视频的帧率
        long frameDuration = 0;

        Muxer muxer = null; //用于合成音频与视频

        try {
            muxer = new Muxer(outFd,Muxer.MediaFileFormat.FORMAT_MPEG4);

            //音轨信息
            audioExtractor.setSource(new Source(audioFd)); //设置视频源
            int audioTrackCount = audioExtractor.getTotalStreams(); //获取数据源的轨道数
            //在此循环轨道数,目的是找到我们想要的音频轨
            for (int i = 0; i < audioTrackCount; i++) {
                Format format = audioExtractor.getStreamFormat(i);//得到指定索引的记录格式
                String mimeType = format.getStringValue(Format.MIME);
                System.out.println("=======mimeType:"+mimeType);
                if (mimeType.startsWith("audio/")) { //找到音轨
                    mainAudioExtractorTrackIndex = i;
                    mainAudioMuxerTrackIndex = muxer.appendTrack(format); //将音轨添加到Muxer,并返回新的轨道
                    mainAudioMaxInputSize = format.getIntValue(Format.MAX_INPUT_SIZE);
                    //得到能获取的有关音频的最大值
                }
            }

            //图像信息
            frameExtractor.setSource(new Source(videoFd)); //设置视频源
            int trackCount = frameExtractor.getTotalStreams();//获取数据源的轨道数
            //在此循环轨道数,目的是找到我们想要的视频轨
            for (int i = 0; i < trackCount; i++) {
                Format vedioFormat = frameExtractor.getStreamFormat(i);
                String vedioMimeType = vedioFormat.getStringValue(Format.MIME);
                if (vedioMimeType.startsWith("video/")) { //找到视频轨
                    frameExtractorTrackIndex = i;
                    frameMuxerTrackIndex = muxer.appendTrack(vedioFormat); //将视频轨添加到Muxer,并返回新的轨道
                    frameMaxInputSize = vedioFormat.getIntValue(Format.MAX_INPUT_SIZE);
                    frameRate = vedioFormat.getIntValue(Format.FRAME_RATE); //获取视频的帧率
                    frameDuration = vedioFormat.getLongValue(Format.DURATION); //获取视频时长
                }
            }

            muxer.start(); //开始合成
            audioExtractor.specifyStream(mainAudioExtractorTrackIndex); //将提供音频的视频选择到音轨上
            BufferInfo bufferInfo = new BufferInfo();
            ByteBuffer audioByteBuffer = ByteBuffer.allocate(mainAudioMaxInputSize);
            while (true) {
                int readSampleSize = audioExtractor.readBuffer(audioByteBuffer, 0);
                //检索当前编码的样本并将其存储在字节缓冲区中
                long audioSampleTime = audioExtractor.getFrameTimestamp(); //获取当前展示样本的时间(提示信息和api文档说单位毫秒,但实际上应该是微秒,注意!)
                if (readSampleSize < 0) { //如果没有可获取的样本则退出循环
                    audioExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
                    break;
                }

                if (audioSampleTime < audioStartTime*1000) { //如果样本时间小于我们想要的开始时间就快进
                    System.out.println("audioSampleTime < audioStartTime:"
                                       + audioSampleTime +","+ audioStartTime*1000);
                    audioExtractor.next(); //推进到下一个样本,类似快进
                    continue;
                }
                //如果样本时间大于结束时间,就退出循环
                if (audioSampleTime > audioEndTime*1000) {
                    System.out.println("audioSampleTime > audioEndTime:" + audioSampleTime 
                                       +","+ audioEndTime*1000);
                    audioExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
                    break;
                }


                //设置样本编码信息
                bufferInfo.size = readSampleSize;
                bufferInfo.offset = 0;
                bufferInfo.bufferType = audioExtractor.getFrameType();
                bufferInfo.timeStamp = audioSampleTime - audioStartTime*1000;

                muxer.writeBuffer(mainAudioMuxerTrackIndex, audioByteBuffer, bufferInfo); //将样本写入
                audioExtractor.next(); //推进到下一个样本,类似快进
                System.out.println("=======正在合成音频...");
            }

            frameExtractor.specifyStream(frameExtractorTrackIndex); //将提供视频图像的视频选择到视频轨上
            BufferInfo vedioBufferInfo = new BufferInfo();
            ByteBuffer videoByteBuffer = ByteBuffer.allocate(frameMaxInputSize);
            while (true) {
                int readSampleSize = frameExtractor.readBuffer(videoByteBuffer, 0);
                //检索当前编码的样本并将其存储在字节缓冲区中
                if (readSampleSize < 0) { //如果没有可获取的样本则退出循环
                    frameExtractor.unspecifyStream(frameExtractorTrackIndex);
                    break;
                }

                long videoSampleTime = frameExtractor.getFrameTimestamp(); //获取当前展示样本的时间(提示信息和api文档说单位毫秒,但实际上应该是微秒,注意!)
                if (videoSampleTime < videoStartTime*1000) { //如果样本时间小于我们想要的开始时间就快进
                    System.out.println("videoSampleTime < videoStartTime:" 
                                       + videoSampleTime +","+ videoStartTime*1000);
                    frameExtractor.next(); //推进到下一个样本,类似快进
                    continue;
                }
                //如果样本时间大于结束时间,就退出循环
                if (videoSampleTime > videoEndTime*1000) {
                    System.out.println("videoSampleTime > videoEndTime:" 
                                       + videoSampleTime +","+ videoEndTime*1000);
                    frameExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
                    break;
                }
                //设置样本编码信息
                vedioBufferInfo.size = readSampleSize;
                vedioBufferInfo.offset = 0;
                vedioBufferInfo.bufferType = frameExtractor.getFrameType();
                vedioBufferInfo.timeStamp += 1000 * 1000 / frameRate; //每帧的时间是 1/frameRate秒,需转成微秒

                muxer.writeBuffer(frameMuxerTrackIndex, videoByteBuffer, vedioBufferInfo); //将样本写入
                frameExtractor.next(); //推进到下一个样本,类似快进
                System.out.println("=======正在合成视频...");
            }
            System.out.println("=======合成完毕");
            return true;
        }catch (Exception e){
            System.out.println("==========combineTwoVideos出错了!");
            return false;
        }finally {
            //释放资源
            audioExtractor.release();
            frameExtractor.release();
            if (muxer != null) {
                muxer.release();
            }
        }
    }
}

毫秒转00:00:00格式字符串工具:

public class DateUtils {
    private static final int ONE_SECONDS_MS = 1000;
    private static final int ONE_MINS_MINUTES = 60;
    private static final int NUMBER = 16;
    private static final String TIME_FORMAT = "%02d";
    private static final String SEMICOLON = ":";

    public static String msToString(int ms) {
        StringBuilder sb = new StringBuilder(NUMBER);
        int seconds = ms / ONE_SECONDS_MS;
        int minutes = seconds / ONE_MINS_MINUTES;
        if (minutes > ONE_MINS_MINUTES) {
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes / ONE_MINS_MINUTES));
            sb.append(SEMICOLON);
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes % ONE_MINS_MINUTES));
            sb.append(SEMICOLON);
        } else {
            sb.append("00:");
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes));
            sb.append(SEMICOLON);
        }

        if (seconds > minutes * ONE_MINS_MINUTES) {
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, seconds - minutes * ONE_MINS_MINUTES));
        } else {
            sb.append("00");
        }
        return sb.toString();
    }
}

视频播放器工具:

public class PlayerHandler {
    private Player videoPlayer;
    private Context context;
    private Runnable videoRunnable;
    private boolean isFirstPlay = true;
    private EventHandler handler = new EventHandler(EventRunner.create());
    private PlayerStateInterface playerStateInterface;
    private PlayerCurrentTimeInterface playerCurrentTimeInterface;
    public static interface PlayerCurrentTimeInterface{
        void updateCurrentTime(int currentTime);
    }

    public void setPlayerCurrentTimeInterface(PlayerCurrentTimeInterface pcti){
        this.playerCurrentTimeInterface = pcti;
    }

    private Timer timer = new Timer();
    private TimerTask timerTask = null;

    public PlayerHandler(Context context) {
        this.context = context;
    }

    public synchronized void startPlay(Source source, Surface surface) {
        if(videoPlayer == null){
            videoPlayer = new Player(context);
        }
        videoPlayer.setPlayerCallback(new VideoCallBack());
        System.out.println("======isFirstPlay:"+isFirstPlay);
        if(isFirstPlay){
            videoRunnable = () -> firstPlay(source, surface);
            handler.postTask(videoRunnable);
            isFirstPlay = false;
        }else{
            if(!videoPlayer.isNowPlaying()){
                handler.removeTask(videoRunnable);
                videoRunnable = () -> play();
                handler.postTask(videoRunnable);
            }
        }
        timer.purge();
        if(timerTask==null){
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    playerCurrentTimeInterface.updateCurrentTime(getCurrentTime());
                }
            };
        }
        timer.schedule(timerTask, 0, 1000);
    }

    private void firstPlay(Source source, Surface surface) {
        videoPlayer.setSource(source);
        videoPlayer.setVideoSurface(surface);
        videoPlayer.prepare();
        videoPlayer.play();
    }

    private void play() {
        if (videoPlayer == null) {
            return;
        }
        videoPlayer.play();
    }

    public synchronized void pausePlay() {
        if (videoPlayer == null) {
            return;
        }
        videoPlayer.pause();
        timerTask.cancel();
        timerTask = null;//如果不重新new,会报异常

    }

    public void rewindTo(long ms) {
        if (videoPlayer == null) {
            return;
        }
        System.out.println("ms:" + ms);
        videoPlayer.rewindTo(ms*1000);
    }

    public void release() {
        if (videoPlayer != null) {
            videoPlayer.stop();
            videoPlayer.release();
            videoPlayer = null;
            timer.cancel();
            timer = null;
        }
    }

    public int getCurrentTime(){
        if (videoPlayer == null) {
            return 0;
        }
        return videoPlayer.getCurrentTime();
    }

    public static interface PlayerStateInterface {
        void updateTotalTime(int totalTime);
    }

    public void setPlayerStateInterface(PlayerStateInterface psi){
        this.playerStateInterface = psi;
    }





    private class VideoCallBack implements Player.IPlayerCallback{

        @Override
        public void onPrepared() {
            playerStateInterface.updateTotalTime(videoPlayer.getDuration());
        }

        @Override
        public void onMessage(int i, int i1) {

        }

        @Override
        public void onError(int i, int i1) {

        }

        @Override
        public void onResolutionChanged(int i, int i1) {

        }

        @Override
        public void onPlayBackComplete() {

        }

        @Override
        public void onRewindToComplete() {
        }

        @Override
        public void onBufferingChange(int i) {

        }

        @Override
        public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {

        }

        @Override
        public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {

        }
    }

}

业务逻辑实现:

MainAbilitySlice:从外部存储公共目录里读取视频文件,调整到分了合成页面或剪切页面:

public class MainAbilitySlice extends AbilitySlice {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        initComponents();
    }

    private void initComponents() {
        Component goCutBtn = findComponentById(ResourceTable.Id_go_cut_btn);
        goCutBtn.setClickedListener(this::goCutBtnFunc);
        Component goSeparateBtn = findComponentById(ResourceTable.Id_go_separate_btn);
        goSeparateBtn.setClickedListener(this::goSeparateBtnFunc);
    }

    private void goSeparateBtnFunc(Component component) {
        Intent intent = new Intent();
        Operation build = new Intent.OperationBuilder()
                .withBundleName(getBundleName())
                .withDeviceId("")
                .withAbilityName(SeparateAbility.class.getName())
                .build();
        intent.setParam("separateFiles",new String[]{"audioFile.mp4","videoFile.mp4"});
        intent.setOperation(build);
        startAbility(intent);
    }

    private void goCutBtnFunc(Component component) {
       //这里可以做的更灵活一定,做一个文件列表读取sd卡上的视频文件,选择要处理的拿出来它的文件名
        Intent intent = new Intent();
        Operation build = new Intent.OperationBuilder()
                .withBundleName(getBundleName())
                .withDeviceId("")
                .withAbilityName(CutAbility.class.getName())
                .build();
        intent.setParam("cutFile","cut.mp4");
        intent.setOperation(build);
        startAbility(intent);
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}

视频分离合成的逻辑:

public class SeparateAbilitySlice extends AbilitySlice {
    private String[] separateFiles;
    private FileDescriptor audioFd;
    private FileDescriptor videoFd;
    private SurfaceProvider audioSurfaceProvider;
    private Surface audioSurface;
    private SurfaceProvider videoSurfaceProvider;
    private Surface videoSurface;
    private PlayerHandler audioPlayerHandler;
    private PlayerHandler videoPlayerHandler;
    private boolean audioClick = false;
    private boolean videoClick = false;
    private int audioTotalTime;
    private int videoTotalTime;
    private Slider audioProgressBar;
    private Slider videoProgressBar;
    private Button startTime1,startTime2,endTime1,endTime2;
    private Text currentTime1,currentTime2;
    private int startAudioTime1,startVideoTimt2,endAudioTime1,endVideoTime2;
    private Button separateBtn;


    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_separate);
        //初始化组件
        initCompoents();
        //拿到参数和分离合成文件
        initParam(intent);
        //使用播放器播放视频
        initSurfaceProvider();
        //设置组件的监听
        initListeners();
    }

    private void initListeners() {
        audioProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
            @Override
            public void onProgressUpdated(Slider slider, int value, boolean isB) {
                getUITaskDispatcher().asyncDispatch(() ->
                        currentTime1.setText(DateUtils.msToString(value)));
            }

            @Override
            public void onTouchStart(Slider slider) {

            }

            @Override
            public void onTouchEnd(Slider slider) {
                if (slider.getProgress() == audioTotalTime) {
                    audioPlayerHandler.release();
                } else {
                    audioPlayerHandler.rewindTo(slider.getProgress());
                }
            }
        });

        videoProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
            @Override
            public void onProgressUpdated(Slider slider, int value, boolean isB) {
                getUITaskDispatcher().asyncDispatch(() ->
                        currentTime2.setText(DateUtils.msToString(value)));
            }

            @Override
            public void onTouchStart(Slider slider) {

            }

            @Override
            public void onTouchEnd(Slider slider) {
                if (slider.getProgress() == videoTotalTime) {
                    videoPlayerHandler.release();
                } else {
                    videoPlayerHandler.rewindTo(slider.getProgress());
                }
            }
        });


        startTime1.setClickedListener(component -> {
            startAudioTime1 = audioPlayerHandler.getCurrentTime();
            startTime1.setText(DateUtils.msToString(startAudioTime1));
        });

        startTime2.setClickedListener(component -> {
            startVideoTimt2 = videoPlayerHandler.getCurrentTime();
            startTime2.setText(DateUtils.msToString(startVideoTimt2));
        });

        endTime1.setClickedListener(component -> {
            endAudioTime1 = audioPlayerHandler.getCurrentTime();
            endTime1.setText(DateUtils.msToString(endAudioTime1));
        });

        endTime2.setClickedListener(component -> {
            endVideoTime2 = videoPlayerHandler.getCurrentTime();
            endTime2.setText(DateUtils.msToString(endVideoTime2));
        });

        separateBtn.setClickedListener(component -> {
            FileDescriptor outFd = StorageFileUtils.getPublicFdForInsert(this
                                       , StorageFileUtils.MediaType.VIDEO, "combineFile.mp4");
            boolean rs = MediaHandler.combineTwoVideos(this,audioFd,startAudioTime1,endAudioTime1
            ,videoFd,startVideoTimt2,endVideoTime2,outFd);
            if (rs){
                getUITaskDispatcher().asyncDispatch(()->{
                   new ToastDialog(this).setText("合成完成").show();
                });
            }
        });
    }

    private void initCompoents() {
        audioProgressBar = (Slider) findComponentById(ResourceTable.Id_progress1);
        videoProgressBar = (Slider) findComponentById(ResourceTable.Id_progress2);
        startTime1 = (Button)findComponentById(ResourceTable.Id_start_time_btn1);
        startTime2 = (Button)findComponentById(ResourceTable.Id_start_time_btn2);
        endTime1 = (Button)findComponentById(ResourceTable.Id_end_time_btn1);
        endTime2 = (Button)findComponentById(ResourceTable.Id_end_time_btn2);
        currentTime1 = (Text) findComponentById(ResourceTable.Id_current_time1);
        currentTime2 = (Text) findComponentById(ResourceTable.Id_current_time2);
        separateBtn = (Button)findComponentById(ResourceTable.Id_separate_btn);
    }


    private void initSurfaceProvider() {
        audioSurfaceProvider = new SurfaceProvider(this);
        audioSurfaceProvider.getSurfaceOps().get().addCallback(new AudioSurfaceCallBack());
        audioSurfaceProvider.pinToZTop(true);
        DirectionalLayout directionalLayout1 =
                (DirectionalLayout) findComponentById(ResourceTable.Id_play_directionalLayout1);
        directionalLayout1.addComponent(audioSurfaceProvider);

        videoSurfaceProvider = new SurfaceProvider(this);
        videoSurfaceProvider.getSurfaceOps().get().addCallback(new VedioSurfaceCallBack());
        videoSurfaceProvider.pinToZTop(true);
        DirectionalLayout directionalLayout2 =
                (DirectionalLayout) findComponentById(ResourceTable.Id_play_directionalLayout2);
        directionalLayout2.addComponent(videoSurfaceProvider);
    }


    private class AudioSurfaceCallBack implements SurfaceOps.Callback{
        @Override
        public void surfaceCreated(SurfaceOps surfaceOps) {
            System.out.println("======audio surfaceCreated");
            if (audioSurfaceProvider.getSurfaceOps().isPresent()) {
                audioSurface = audioSurfaceProvider.getSurfaceOps().get().getSurface();
                initAudioPlayer(audioSurface);

            }
        }
        @Override
        public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
        @Override
        public void surfaceDestroyed(SurfaceOps surfaceOps) {}
    }

    private class VedioSurfaceCallBack implements SurfaceOps.Callback{
        @Override
        public void surfaceCreated(SurfaceOps surfaceOps) {
            System.out.println("======video surfaceCreated");
            if (videoSurfaceProvider.getSurfaceOps().isPresent()) {
                videoSurface = videoSurfaceProvider.getSurfaceOps().get().getSurface();
                initVideoPlayer(videoSurface);
            }
        }
        @Override
        public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
        @Override
        public void surfaceDestroyed(SurfaceOps surfaceOps) {}
    }

    private void initAudioPlayer(Surface audioSurface) {
        System.out.println("======initAudioPlayer");
        audioPlayerHandler = new PlayerHandler(getApplicationContext());
        audioPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
            @Override
            public void updateTotalTime(int totalTime) {
                System.out.println("======slice totalTime:" + totalTime);
                audioTotalTime = totalTime;
                getUITaskDispatcher().asyncDispatch(()->{
                    Text time1 = (Text)findComponentById(ResourceTable.Id_end_time1);
                    time1.setText(DateUtils.msToString(totalTime));
                    audioProgressBar.setMaxValue(totalTime);
                });
            }
        });
        audioPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
            @Override
            public void updateCurrentTime(int currentTime) {
                getUITaskDispatcher().asyncDispatch(()->{
                    audioProgressBar.setProgressValue(currentTime);
                });
            }
        });
        Source source = new Source(audioFd);
        audioSurfaceProvider.setClickedListener(component -> {
            if(!audioClick){
                audioClick = !audioClick;
                audioPlayerHandler.startPlay(source, audioSurface);
             }else{
                audioClick = !audioClick;
                audioPlayerHandler.pausePlay();
            }
        });
    }

    private void initVideoPlayer(Surface audioSurface) {
        System.out.println("======initVideoPlayer");
        videoPlayerHandler = new PlayerHandler(getApplicationContext());
        videoPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
            @Override
            public void updateTotalTime(int totalTime) {
                videoTotalTime = totalTime;
                getUITaskDispatcher().asyncDispatch(()->{
                    Text time2 = (Text)findComponentById(ResourceTable.Id_end_time2);
                    time2.setText(DateUtils.msToString(totalTime));
                    videoProgressBar.setMaxValue(totalTime);
                });
            }
        });
        videoPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
            @Override
            public void updateCurrentTime(int currentTime) {
                videoProgressBar.setProgressValue(currentTime);
            }
        });
        Source source = new Source(videoFd);
        videoSurfaceProvider.setClickedListener(component -> {
            if(!videoClick){
                videoClick = !videoClick;
                videoPlayerHandler.startPlay(source, videoSurface);
            }else{
                videoClick = !videoClick;
                videoPlayerHandler.pausePlay();
            }
        });
    }

    private void initParam(Intent intent) {
        if(intent != null){
            separateFiles = intent.getStringArrayParam("separateFiles");
            System.out.println("=====" + Arrays.toString(separateFiles));
            if(separateFiles.length == 2){
                audioFd = StorageFileUtils.getPublicFileFdForRead(
                        this,
                        StorageFileUtils.MediaType.VIDEO,
                        separateFiles[0]
                );
                videoFd = StorageFileUtils.getPublicFileFdForRead(
                        this,
                        StorageFileUtils.MediaType.VIDEO,
                        separateFiles[1]
                );
            }
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        audioPlayerHandler.release();
        videoSurfaceProvider.release();
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

}

视频截取逻辑代码:

public class CutAbilitySlice extends AbilitySlice {
    private Slider videoProgressBar;
    private Button startTime;
    private Button endTime;
    private Button cutBtn;
    private String cutFile;
    private FileDescriptor videoFd;
    private SurfaceProvider videoSurfaceProvider;
    private Surface videoSurface;
    private PlayerHandler videoPlayerHandler;
    private int videoTotalTime;
    private boolean videoClick;
    private Text currentTimeText;
    private int startVideoTime;
    private int endVideoTime;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_cut);
        //初始化组件
        initCompoents();
        //拿到参数和分离合成文件
        initParam(intent);
        //使用播放器播放视频
        initSurfaceProvider();
        //设置组件的监听
        initListeners();
    }

    private void initCompoents() {
        videoProgressBar = (Slider) findComponentById(ResourceTable.Id_progress);
        startTime = (Button)findComponentById(ResourceTable.Id_start_time_btn);
        endTime = (Button)findComponentById(ResourceTable.Id_end_time_btn);
        cutBtn = (Button)findComponentById(ResourceTable.Id_cut_btn);
        currentTimeText = (Text) findComponentById(ResourceTable.Id_current_time);
    }

    private void initParam(Intent intent) {
        if(intent != null){
            cutFile = intent.getStringParam("cutFile");
            videoFd = StorageFileUtils.getPublicFileFdForRead(
                    this,
                    StorageFileUtils.MediaType.VIDEO,
                    cutFile
            );
        }
    }

    private void initSurfaceProvider() {
        videoSurfaceProvider = new SurfaceProvider(this);
        videoSurfaceProvider.getSurfaceOps().get().addCallback(new VedioSurfaceCallBack());
        videoSurfaceProvider.pinToZTop(true);
        DirectionalLayout directionalLayout =
                (DirectionalLayout) findComponentById(ResourceTable.Id_cut_play_directionalLayout);
        directionalLayout.addComponent(videoSurfaceProvider);
    }

    private class VedioSurfaceCallBack implements SurfaceOps.Callback{
        @Override
        public void surfaceCreated(SurfaceOps surfaceOps) {
            System.out.println("======video surfaceCreated");
            if (videoSurfaceProvider.getSurfaceOps().isPresent()) {
                videoSurface = videoSurfaceProvider.getSurfaceOps().get().getSurface();
                initVideoPlayer(videoSurface);
            }
        }
        @Override
        public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
        @Override
        public void surfaceDestroyed(SurfaceOps surfaceOps) {}
    }

    private void initVideoPlayer(Surface audioSurface) {
        System.out.println("======initVideoPlayer");
        videoPlayerHandler = new PlayerHandler(getApplicationContext());
        videoPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
            @Override
            public void updateTotalTime(int totalTime) {
                videoTotalTime = totalTime;
                getUITaskDispatcher().asyncDispatch(()->{
                    Text time = (Text)findComponentById(ResourceTable.Id_end_time);
                    time.setText(DateUtils.msToString(totalTime));
                    videoProgressBar.setMaxValue(totalTime);
                });
            }
        });
        videoPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
            @Override
            public void updateCurrentTime(int currentTime) {
                videoProgressBar.setProgressValue(currentTime);
            }
        });
        Source source = new Source(videoFd);
        videoSurfaceProvider.setClickedListener(component -> {
            if(!videoClick){
                videoClick = !videoClick;
                videoPlayerHandler.startPlay(source, videoSurface);
            }else{
                videoClick = !videoClick;
                videoPlayerHandler.pausePlay();
            }
        });
    }

    private void initListeners() {
        videoProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
            @Override
            public void onProgressUpdated(Slider slider, int value, boolean isB) {
                getUITaskDispatcher().asyncDispatch(() ->
                        currentTimeText.setText(DateUtils.msToString(value)));
            }

            @Override
            public void onTouchStart(Slider slider) {

            }

            @Override
            public void onTouchEnd(Slider slider) {
                if (slider.getProgress() == videoTotalTime) {
                    videoPlayerHandler.release();
                } else {
                    videoPlayerHandler.rewindTo(slider.getProgress());
                }
            }
        });




        startTime.setClickedListener(component -> {
            startVideoTime = videoPlayerHandler.getCurrentTime();
            startTime.setText(DateUtils.msToString(startVideoTime));
        });



        endTime.setClickedListener(component -> {
            endVideoTime = videoPlayerHandler.getCurrentTime();
            endTime.setText(DateUtils.msToString(endVideoTime));
        });

        cutBtn.setClickedListener(component -> {
            FileDescriptor outFd = StorageFileUtils.getPublicFdForInsert(this
                                               , StorageFileUtils.MediaType.VIDEO, "cutFileRs.mp4");
            boolean rs = MediaHandler.combineTwoVideos(this,videoFd,startVideoTime,endVideoTime
                    ,videoFd,startVideoTime,endVideoTime,outFd);
            if (rs){
                getUITaskDispatcher().asyncDispatch(()->{
                    new ToastDialog(this).setText("剪切完成").show();
                });
            }
        });
    }

    @Override
    protected void onStop() {
        super.onStop();
        videoSurfaceProvider.release();
    }


    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
demo.zip 1.68M 60次下载
已于2022-1-29 23:40:16修改
3
收藏 2
回复
举报
2条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

感谢钟老师带来的精彩授课,回放正在加紧制作中。

回复
2022-1-20 11:48:41
物联风景
物联风景

先收藏一下

回复
2022-1-21 10:02:55
回复
    相关推荐