1. 程式人生 > >Android實現視訊剪下、視訊拼接以及音視訊合併

Android實現視訊剪下、視訊拼接以及音視訊合併

              因公司專案有需求,要實現視訊剪下,視訊拼接以及音視訊合併的功能,自己通過在網上查詢大量的資料終於把功能實現了,把實現的公共類提取出來,以便以後複習鞏固。

使用map4parser作為視訊處理包,android studio引入compile 'com.googlecode.mp4parser:isoparser:1.1.21'//視訊處理

工具類

一、視訊處理工具類  

/**
 * mp4處理公共類
* Created by lxy on 17-4-21.
 */
public class Mp4ParseUtil {


    /**
     * Mp4檔案集合進行追加合併(按照順序一個一個拼接起來)
     *
     * @param mp4PathList [輸入]Mp4檔案路徑的集合(支援m4a)(不支援wav)
     * 
@param outPutPath [輸出]結果檔案全部名稱包含字尾(比如.mp4) * @throws IOException 格式不支援等情況丟擲異常 */ public static void appendMp4List(List<String> mp4PathList, String outPutPath){ try { List<Movie> mp4MovieList = new ArrayList<>();// Movie物件集合[輸入] for (String mp4Path : mp4PathList) {//
將每個檔案路徑都構建成一個Movie物件 mp4MovieList.add(MovieCreator.build(mp4Path)); } List<Track> audioTracks = new LinkedList<>();// 音訊通道集合 List<Track> videoTracks = new LinkedList<>();// 視訊通道集合 for (Movie mp4Movie : mp4MovieList) {// Movie物件集合進行迴圈 for (Track inMovieTrack : mp4Movie.getTracks()) { if
("soun".equals(inMovieTrack.getHandler())) {// Movie物件中取出音訊通道 audioTracks.add(inMovieTrack); } if ("vide".equals(inMovieTrack.getHandler())) {// Movie物件中取出視訊通道 videoTracks.add(inMovieTrack); } } } Movie resultMovie = new Movie();// 結果Movie物件[輸出] if (!audioTracks.isEmpty()) {// 將所有音訊通道追加合併 resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()]))); } if (!videoTracks.isEmpty()) {// 將所有視訊通道追加合併 resultMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()]))); } Container outContainer = new DefaultMp4Builder().build(resultMovie);// 將結果Movie物件封裝進容器 FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel(); outContainer.writeContainer(fileChannel);// 將容器內容寫入磁碟 fileChannel.close(); }catch(Exception e){ e.printStackTrace(); } } /** * AAC檔案集合進行追加合併(按照順序一個一個拼接起來) * * @param aacPathList [輸入]AAC檔案路徑的集合(不支援wav) * @param outPutPath [輸出]結果檔案全部名稱包含字尾(比如.aac) * @throws IOException 格式不支援等情況丟擲異常 */ public static void appendAacList(List<String> aacPathList, String outPutPath){ try{ List<Track> audioTracks = new LinkedList<>();// 音訊通道集合 for (int i = 0; i < aacPathList.size(); i++) {// 將每個檔案路徑都構建成一個AACTrackImpl物件 audioTracks.add(new AACTrackImpl(new FileDataSourceImpl(aacPathList.get(i)))); } Movie resultMovie = new Movie();// 結果Movie物件[輸出] if (!audioTracks.isEmpty()) {// 將所有音訊通道追加合併 resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()]))); } Container outContainer = new DefaultMp4Builder().build(resultMovie);// 將結果Movie物件封裝進容器 FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel(); outContainer.writeContainer(fileChannel);// 將容器內容寫入磁碟 fileChannel.close(); }catch (Exception e){ e.printStackTrace(); } } private static List<Movie> moviesList = new ArrayList<>(); private static List<Track> videoTracks = new ArrayList<>(); private static List<Track> audioTracks = new ArrayList<>(); //將兩個mp4視訊進行拼接 public static void appendMp4(List<String> mMp4List,String outputpath){ try { for (int i=0;i<mMp4List.size();i++) { Movie movie=MovieCreator.build(mMp4List.get(i)); moviesList.add(movie); } } catch (IOException e) { e.printStackTrace(); } for (Movie m : moviesList) { for (Track t : m.getTracks()) { if (t.getHandler().equals("soun")) { audioTracks.add(t); } if (t.getHandler().equals("vide")) { videoTracks.add(t); } } } Movie result = new Movie(); try { if (audioTracks.size() > 0) { result.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()]))); } if (videoTracks.size() > 0) { result.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()]))); } } catch (IOException e) { e.printStackTrace(); } Container out = new DefaultMp4Builder().build(result); try { FileChannel fc = new FileOutputStream(new File(outputpath)).getChannel(); out.writeContainer(fc); fc.close(); } catch (Exception e) { e.printStackTrace(); } moviesList.clear(); } /** * AAC MP4 進行混合[替換了視訊的音軌] * * @param aacPath .aac * @param mp4Path .mp4 * @param outPath .mp4 */ public static boolean muxAacMp4(String aacPath, String mp4Path, String outPath) { boolean flag=false; try { AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(aacPath)); Movie videoMovie = MovieCreator.build(mp4Path); Track videoTracks = null;// 獲取視訊的單純視訊部分 for (Track videoMovieTrack : videoMovie.getTracks()) { if ("vide".equals(videoMovieTrack.getHandler())) { videoTracks = videoMovieTrack; } } Movie resultMovie = new Movie(); resultMovie.addTrack(videoTracks);// 視訊部分 resultMovie.addTrack(aacTrack);// 音訊部分 Container out = new DefaultMp4Builder().build(resultMovie); FileOutputStream fos = new FileOutputStream(new File(outPath)); out.writeContainer(fos.getChannel()); fos.close(); flag=true; Log.e("update_tag","merge finish"); } catch (Exception e) { e.printStackTrace(); flag=false; } return flag; } /** * M4A MP4 進行混合[替換了視訊的音軌] * * @param m4aPath .m4a[同樣可以使用.mp4] * @param mp4Path .mp4 * @param outPath .mp4 */ public static void muxM4AMp4(String m4aPath, String mp4Path, String outPath) throws IOException { Movie audioMovie = MovieCreator.build(m4aPath); Track audioTracks = null;// 獲取視訊的單純音訊部分 for (Track audioMovieTrack : audioMovie.getTracks()) { if ("soun".equals(audioMovieTrack.getHandler())) { audioTracks = audioMovieTrack; } } Movie videoMovie = MovieCreator.build(mp4Path); Track videoTracks = null;// 獲取視訊的單純視訊部分 for (Track videoMovieTrack : videoMovie.getTracks()) { if ("vide".equals(videoMovieTrack.getHandler())) { videoTracks = videoMovieTrack; } } Movie resultMovie = new Movie(); resultMovie.addTrack(videoTracks);// 視訊部分 resultMovie.addTrack(audioTracks);// 音訊部分 Container out = new DefaultMp4Builder().build(resultMovie); FileOutputStream fos = new FileOutputStream(new File(outPath)); out.writeContainer(fos.getChannel()); fos.close(); } /** * 分離mp4視訊的音訊部分,只保留視訊部分 * * @param mp4Path .mp4 * @param outPath .mp4 */ public static void splitMp4(String mp4Path, String outPath){ try{ Movie videoMovie = MovieCreator.build(mp4Path); Track videoTracks = null;// 獲取視訊的單純視訊部分 for (Track videoMovieTrack : videoMovie.getTracks()) { if ("vide".equals(videoMovieTrack.getHandler())) { videoTracks = videoMovieTrack; } } Movie resultMovie = new Movie(); resultMovie.addTrack(videoTracks);// 視訊部分 Container out = new DefaultMp4Builder().build(resultMovie); FileOutputStream fos = new FileOutputStream(new File(outPath)); out.writeContainer(fos.getChannel()); fos.close(); }catch (Exception e){ e.printStackTrace(); } } /** * 分離mp4的視訊部分,只保留音訊部分 * * @param mp4Path .mp4 * @param outPath .aac */ public static void splitAac(String mp4Path, String outPath){ try{ Movie videoMovie = MovieCreator.build(mp4Path); Track videoTracks = null;// 獲取音訊的單純視訊部分 for (Track videoMovieTrack : videoMovie.getTracks()) { if ("soun".equals(videoMovieTrack.getHandler())) { videoTracks = videoMovieTrack; } } Movie resultMovie = new Movie(); resultMovie.addTrack(videoTracks);// 音訊部分 Container out = new DefaultMp4Builder().build(resultMovie); FileOutputStream fos = new FileOutputStream(new File(outPath)); out.writeContainer(fos.getChannel()); fos.close(); }catch (Exception e){ e.printStackTrace(); } } /** * 分離mp4視訊的音訊部分,只保留視訊部分 * * @param mp4Path .mp4 * @param mp4OutPath mp4視訊輸出路徑 * @param aacOutPath aac視訊輸出路徑 */ public static void splitVideo(String mp4Path, String mp4OutPath,String aacOutPath){ try{ Movie videoMovie = MovieCreator.build(mp4Path); Track videTracks = null;// 獲取視訊的單純視訊部分 Track sounTracks = null;// 獲取視訊的單純音訊部分 for (Track videoMovieTrack : videoMovie.getTracks()) { if ("vide".equals(videoMovieTrack.getHandler())) { videTracks = videoMovieTrack; } if ("soun".equals(videoMovieTrack.getHandler())) { sounTracks = videoMovieTrack; } } Movie videMovie = new Movie(); videMovie.addTrack(videTracks);// 視訊部分 Movie sounMovie = new Movie(); sounMovie.addTrack(sounTracks);// 音訊部分 // 視訊部分 Container videout = new DefaultMp4Builder().build(videMovie); FileOutputStream videfos = new FileOutputStream(new File(mp4OutPath)); videout.writeContainer(videfos.getChannel()); videfos.close(); // 音訊部分 Container sounout = new DefaultMp4Builder().build(sounMovie); FileOutputStream sounfos = new FileOutputStream(new File(aacOutPath)); sounout.writeContainer(sounfos.getChannel()); sounfos.close(); }catch (Exception e){ e.printStackTrace(); } } /** * Mp4 新增字幕 * * @param mp4Path .mp4 新增字幕之前 * @param outPath .mp4 新增字幕之後 */ public static void addSubtitles(String mp4Path, String outPath) throws IOException { Movie videoMovie = MovieCreator.build(mp4Path); TextTrackImpl subTitleEng = new TextTrackImpl();// 例項化文字通道物件 subTitleEng.getTrackMetaData().setLanguage("eng");// 設定元資料(語言) subTitleEng.getSubs().add(new TextTrackImpl.Line(0, 1000, "Five"));// 引數時間毫秒值 subTitleEng.getSubs().add(new TextTrackImpl.Line(1000, 2000, "Four")); subTitleEng.getSubs().add(new TextTrackImpl.Line(2000, 3000, "Three")); subTitleEng.getSubs().add(new TextTrackImpl.Line(3000, 4000, "Two")); subTitleEng.getSubs().add(new TextTrackImpl.Line(4000, 5000, "one")); subTitleEng.getSubs().add(new TextTrackImpl.Line(5001, 5002, " "));// 省略去測試 videoMovie.addTrack(subTitleEng);// 將字幕通道新增進視訊Movie物件中 Container out = new DefaultMp4Builder().build(videoMovie); FileOutputStream fos = new FileOutputStream(new File(outPath)); out.writeContainer(fos.getChannel()); fos.close(); } /** * MP4 切割 * * @param mp4Path .mp4 * @param fromSample 起始位置 不是傳入的秒數 * @param toSample 結束位置 不是傳入的秒數 * @param outPath .mp4 */ public static void cropMp4(String mp4Path, long fromSample, long toSample, String outPath){ try{ Movie mp4Movie = MovieCreator.build(mp4Path); Track videoTracks = null;// 獲取視訊的單純視訊部分 for (Track videoMovieTrack : mp4Movie.getTracks()) { if ("vide".equals(videoMovieTrack.getHandler())) { videoTracks = videoMovieTrack; } } Track audioTracks = null;// 獲取視訊的單純音訊部分 for (Track audioMovieTrack : mp4Movie.getTracks()) { if ("soun".equals(audioMovieTrack.getHandler())) { audioTracks = audioMovieTrack; } } Movie resultMovie = new Movie(); resultMovie.addTrack(new AppendTrack(new CroppedTrack(videoTracks, fromSample, toSample)));// 視訊部分 resultMovie.addTrack(new AppendTrack(new CroppedTrack(audioTracks, fromSample, toSample)));// 音訊部分 Container out = new DefaultMp4Builder().build(resultMovie); FileOutputStream fos = new FileOutputStream(new File(outPath)); out.writeContainer(fos.getChannel()); fos.close(); }catch(Exception e){ e.printStackTrace(); } } }

二、視訊剪下工具類

/**
 * 視訊剪下
* Created by lxy on 17-4-21.
 */
public class VideoClip {

    private static final String TAG = "VideoClip";
    private String filePath;//視訊路徑
private String workingPath;//輸出路徑
private String outName;//輸出檔名
private double startTime;//剪下起始時間
private double endTime;//剪下結束時間
public void setFilePath(String filePath) {
        this.filePath = filePath;
    }


    public void setWorkingPath(String workingPath) {
        this.workingPath = workingPath;
    }

    public void setOutName(String outName) {
        this.outName = outName;
    }

    public void setEndTime(double endTime) {
        this.endTime = endTime / 1000;
    }

    public void setStartTime(double startTime) {
        this.startTime = startTime / 1000;
    }

    public synchronized void clip() {
        try {
            //將要剪輯的視訊檔案
Movie movie = MovieCreator.build(filePath);

            List<Track> tracks = movie.getTracks();
            movie.setTracks(new LinkedList<Track>());
            //時間是否修正
boolean timeCorrected = false;

            //計算並換算剪下時間
for (Track track : tracks) {
                if (track.getSyncSamples() != null
&& track.getSyncSamples().length > 0) {
                    if (timeCorrected) {
                        throw new RuntimeException(
                                "The startTime has already been corrected by another track with SyncSample. Not Supported.");
                    }
                    //true,false表示短擷取;false,true表示長擷取
startTime = VideoHelper.correctTimeToSyncSample(track, startTime, false);//修正後的開始時間
endTime = VideoHelper.correctTimeToSyncSample(track, endTime, true);     //修正後的結束時間
timeCorrected = true;
                }
            }
            //根據換算到的開始時間和結束時間來擷取視訊
for (Track track : tracks) {
                long currentSample = 0; //視訊擷取到的當前的位置的時間
double currentTime = 0; //視訊的時間長度
double lastTime = -1;    //上次擷取到的最後的時間
long startSample1 = -1;  //擷取開始的時間
long endSample1 = -1;    //擷取結束的時間
//設定開始剪輯的時間和結束剪輯的時間  避免超出視訊總長
for (int i = 0; i < track.getSampleDurations().length; i++) {
                    long delta = track.getSampleDurations()[i];
                    if (currentTime > lastTime && currentTime <= startTime) {
                        startSample1 = currentSample;//編輯開始的時間
}
                    if (currentTime > lastTime && currentTime <= endTime) {
                        endSample1 = currentSample;  //編輯結束的時間
}
                    lastTime = currentTime;          //上次擷取到的時間(避免在視訊最後位置了還在增加編輯結束的時間)
currentTime += (double) delta
                            / (double) track.getTrackMetaData().getTimescale();//視訊的時間長度
currentSample++;                 //當前位置+1
}
                movie.addTrack(new CroppedTrack(track, startSample1, endSample1));// 建立一個新的視訊檔案
}

            //合成視訊mp4
Container out = new DefaultMp4Builder().build(movie);
            File storagePath = new File(workingPath);
            storagePath.mkdirs();
            FileOutputStream fos = new FileOutputStream(new File(storagePath, outName));
            FileChannel fco = fos.getChannel();
            out.writeContainer(fco);
            //關閉流
fco.close();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}
/**
 * Created by lxy on 17-4-17.
 */
public class VideoHelper {

    //換算剪下時間
public static double correctTimeToSyncSample(Track track, double cutHere,
                                                 boolean next) {
        double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
        long currentSample = 0;
        double currentTime = 0;
        for (int i = 0; i < track.getSampleDurations().length; i++) {
            long delta = track.getSampleDurations()[i];
            if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
                timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(),
                        currentSample + 1)] = currentTime;
            }
            currentTime += (double) delta
                    / (double) track.getTrackMetaData().getTimescale();
            currentSample++;
        }
        double previous = 0;
        for (double timeOfSyncSample : timeOfSyncSamples) {
            if (timeOfSyncSample > cutHere) {
                if (next) {
                    return timeOfSyncSample;
                } else {
                    return previous;
                }
            }
            previous = timeOfSyncSample;
        }
        return timeOfSyncSamples[timeOfSyncSamples.length - 1];
    }

}

三、實現錄音功能的類

/**
 * 實現錄音功能的類
*/
public class MediaRecorderUtil {


    private MediaRecorder mediarecorder=null;//錄音功能公共類
private static final String recordFilePath = FileUtil.getRecorderDir()+"/recorder.aac";
    private RecorderThread recorderThread=null;
    private static final String UPDATE_TAG="update_tag";


    /**
     * 開啟錄音功能
*/
public void recorderStart(){

        //啟動midiarecoder錄音
recorderThread=new RecorderThread();
        recorderThread.start();

    }


    /**
     * 停止錄音,並保存錄音
*/
public void recorderSave(){

        if(mediarecorder!=null){
            mediarecorder.stop();
            mediarecorder.release();
            mediarecorder=null;

            if(recorderThread!=null){
                recorderThread=null;
            }
            Log.e(UPDATE_TAG,"Thread stop voice and save...");
        }

    }


    //開啟錄音功能執行緒
class RecorderThread extends Thread{

        @Override
public void run() {
            super.run();
            try {

                //建立保存錄音檔案
File file=new File(recordFilePath);

                if (mediarecorder==null) {
                    mediarecorder=new MediaRecorder();//
            
           

相關推薦

Android實現視訊視訊拼接以及視訊合併

              因公司專案有需求,要實現視訊剪下,視訊拼接以及音視訊合併的功能,自己通過在網上查詢大量的資料終於把功能實現了,把實現的公共類提取出來,以便以後複習鞏固。使用map4parser作為視訊處理包,android studio引入compile 'co

iOS 視訊旋轉,視訊新增音訊新增水印,視訊匯出

概述 視訊處理主要是用到以下這幾個類 AVMutableComposition、 AVMutableVideoComposition、 AVMutableAudioMix、 AVMutableVideoCompositionInstruction、 AVMuta

6_ffmpeg視訊處理工具(視訊轉碼合併......)(20181212)

ffmpeg視訊處理工具(視訊剪下、轉碼、合併......) 1、ffmpeg的下載和安裝 2、ffmpeg常用處理功能 1、視訊分離成圖片序列:video—to—images(即視訊幀) 2、圖片序列合成視訊:images—to—vi

Android 實現音樂功能 可以選擇起始點和結束點 也可以同時拖動塊級元素

先看一下最終的效果使用者可以拖動兩邊來改變要擷取音樂的長度,也可以直接拖紅線來改變要擷取音樂的位置唯一的難點就是要計算出當前選擇了多少秒,還有拖動時候的操作,我是直接建立的佈局元素,通過onlayout()方法來改變控制元件的位置,廢話不多說,直接上部分原始碼之前上傳的程式碼

用 ffmpeg 實現批量視訊

一直對編輯視訊很有興趣,上大學的時候學了一些視訊剪輯的軟體,已經好久沒用了,現在也都忘的差不多了。前段時間弄了個頭條號,想發一些視訊,就又想做視訊這回事了,要不還真快忘記自己曾經做過一些小視訊了(捂臉)。其實現在想要做的視訊,不需要做很多特效,也不需要做配字幕等這些內容,所以不是很難,但

FFmpeg:視訊轉碼合併播放速調整

安裝去官網按提示安裝即可,支援三大作業系統。唯一要吐槽的是,Ubuntu 14.04 等較新的版本,從預設軟體列表裡移除了 ffmpeg,轉而支援 libav,可是 libav 又沒弄好,很難用——開源社群瞎折騰起來真是無力吐槽。2017 更新: Ubuntu 16.04,F

利用MediaExtractor和MediaMuxer實現視訊

客戶要在android手機上做個能視訊剪下的app,由於視訊源只是MP4,所以就想到了用MediaExtractor和MediaMuxer來實現功能,直接上程式碼。 public class VideoDecoder { private final

視訊成影象儲存到本地

#include<opencv2\opencv.hpp> #include <iostream> #include <stdio.h> #include<fstream> using namespace std; using names

Android實現RecyclerView的拉刷新和上拉載入很多其它

listen gre scheme void fadein 有一個 hot [] study 需求 先上效果圖, Material Design風格的下拉刷新和上拉載入很多其它。 源代碼地址(歡迎star) https://github.com

Node.js中的fs.rename();複製檔案;

/*     重新命名:fs.rename();     fs.rename(oldPath,newPath,callback)     oldPath:代表原來路徑的名稱;     newPath:代表修改後的路徑的名

android實現正方形的ImageViewLayout等(即高度適應寬度或者寬度適應高度)

有時候我們希望在一個頁面中水平放置若干個正方形的ImageView,其總寬度為整個螢幕。如果我們設定每個ImageView的高度和者寬度均為若干個dp,因為不知道螢幕尺寸,就可能導致顯示不全或者有剩餘空間。可以通過以下這個方法設定ImageView其為正方形 首先,建立一個

QOS FEC NACK 實時視訊傳輸庫測試報告(聲網騰訊實時視訊測試)

目錄 實驗環境 測試項說明 測試結果 競品分析 總結                     QOS FEC NACK 實時音視訊傳輸庫測試報告 QOS-FEC-NACK傳輸庫簡介 QOS-FEC-NACK是一套集FEC前向糾錯、

使用PIL圖片和拼接圖片

因工作需要,接觸到了PIL這個包,看其他人的部落格踩了一些坑,有些部落格並沒有註明各個位置引數的含義,今天我就將他補全 切圖    1.  首先先下載一張圖片,我使用的是1200*1200畫素的圖片,將它放置在G盤的img目錄下                2.  

MFC對檔案及資料夾的操作(複製刪除建立資料夾,寫檔案)

1 void CFileOperationDlg::OnButtonCopy() 2 { 3 // TODO: Add your control notification handler code here 4 UpdateData(TRUE); 5 CString m_

C# 複製貼上

1、複製,文字框中選定內容到剪下板: Clipboard.SetDataObject(textBox1.SelectedText); 2、剪下,文字框中當前選中的內容  textBox1.Cut(); 3、貼上,至文字框中 textBox1.Text = (

android實現可自由移動監聽點選事件的懸浮窗

最近因為專案需要,自己實現了個可以自由移動,並且長按可以跳出一個控制播放的,額,大的懸浮窗。 好,開始吧。首先我們先聊許可權,懸浮窗需要在manifest中宣告一個許可權: <uses-permission android:name="androi

visio 2010裡面形狀的聯合組合拆分相交剪除功能在這裡

今天要畫一個圖,總是找不到visio2010裡面的形狀的“聯合”、“組合”、“拆分”、“相交”、“剪除”等功能,在百度和google上都無法搜到答案,大家都是在提這個問題(2003裡面的這些功能區哪裡了,是不是微軟給刪除了等等),不過就是沒有人回答。只好去英文的網站上去找答

Selenium+Python測試鍵盤操作---全選複製貼上

''' 鍵盤操作--將百度搜索的關鍵字複製到必應中進行搜尋 --匯入模組from selenium.webdriver.common.keys import Keys --剪下 --複製 ''' from selenium import webdriver from sele

linux資料夾的建立複製重新命名清空刪除命令和查詢

在home目錄下有wwwroot目錄,wwwroot下有sinozzz目錄,即/home/wwwroot/sinozzz 一、目錄建立 在/home/wwwroot目錄下新建一個sinozzz123的資料夾 mkdir /home/wwwroot/sinozzz123 二、目錄複製

記事本(含複製貼上全選撤銷)核心設計

}                    br.close();對文字的編輯功能需要用到Clipboard類,也就是剪貼簿。通過clipboard = getToolkit().getSystemClipboard();獲取系統的剪貼簿,也就當系統剪貼簿上有內容的時候,同樣可以被用到自己編寫的記事本軟體中。複