在之前的教程中,我對視頻處理Xuggler進行了簡短介紹 。 在這一部分中,我們將看到Xuggler和FFmpeg提供的一些更令人興奮的功能,例如視頻轉碼和媒體修改。 不要忘記Xuggler是一個Java庫,可用于實時解壓縮,處理和壓縮錄制的視頻或實時視頻。
Xuggler提供了兩種不同的編程API,可用于同一目的。 首先,我們有MediaTool API :
MediaTool是一個簡單的應用程序編程接口(API),用于對Java中的視頻進行解碼,編碼和修改。 MediaTool隱藏了許多容器,編解碼器和其他內容的細節,因此您可以專注于媒體而不是工具。 就是說,MediaTool仍然提供對基礎Xuggler對象的訪問,因此,如果需要,您可以進行精細的控制。
還有Xuggler Advanced API ,它使您可以深入研究視頻操作的細節,但又增加了一層復雜性。
首先,我們將使用MediaTool API ,在隨后的教程中,我們還將處理Advanced API。
讓我們開始將媒體從一種格式轉碼為另一種格式。 代碼轉換是一種編碼到另一種編碼的直接數模轉換。 通常在目標設備不支持格式或存儲容量有限(要求減小文件大小)或將不兼容或過時的數據轉換為更好支持的格式或現代格式的情況下執行此操作。 轉碼通常是一個有損過程 ,其中“有損”壓縮是一種數據編碼方法,為了達到其目標,它會丟棄(丟失)某些數據,結果是,對數據進行解壓縮會產生與原始數據不同的內容,盡管足夠相似,以某種方式有用。
讓我們看一些用于轉碼的高級代碼,稍后我將詳細解釋。
package com.javacodegeeks.xuggler;import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.IMediaViewer;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;public class TranscodingExample {private static final String inputFilename = "c:/myvideo.mp4";private static final String outputFilename = "c:/myvideo.flv";public static void main(String[] args) {// create a media readerIMediaReader mediaReader = ToolFactory.makeReader(inputFilename);// create a media writerIMediaWriter mediaWriter = ToolFactory.makeWriter(outputFilename, mediaReader);// add a writer to the reader, to create the output filemediaReader.addListener(mediaWriter);// create a media viewer with stats enabledIMediaViewer mediaViewer = ToolFactory.makeViewer(true);// add a viewer to the reader, to see the decoded mediamediaReader.addListener(mediaViewer);// read and decode packets from the source file and// and dispatch decoded audio and video to the writerwhile (mediaReader.readPacket() == null) ;}}
只需幾行代碼,我們就可以將MPEG-4輸入文件轉換為FLV文件。 我們首先創建一個IMediaReader ,用于讀取和解碼媒體。 它打開一個媒體容器,從中讀取數據包,解碼數據,然后將有關數據的信息分發到任何已注冊的IMediaListener對象。 這是IMediaWriter類起作用的地方。 它對媒體進行編碼和解碼,同時處理音頻和視頻流。 為了使事情變得更有趣,我們還將IMediaViewer附加到我們的閱讀器上。 它用作調試工具,使我們可以在解碼視頻時觀看視頻。 此外,在此過程中,我們還會看到各種統計信息。 請注意,該類處于實驗模式,這意味著它有一些可能會掛起的錯誤,因此請謹慎處理。
本質上,使用上面的代碼,我們將兩個偵聽器IMediaWriter和IMediaViewer附加到IMediaReader對象,并在讀取器讀取和解碼源文件中的數據包的同時處理回調。 這是在“ while”循環中執行的。 如果我們使用示例輸入文件運行該應用程序,將顯示類似于以下的屏幕:
該過程完成后(由于我們正在同時實時查看,因此該過程將與源視頻文件一樣長),將以FLV格式創建一個新的輸出文件。
讓我們從命令行使用Ffmpeg來比較輸入和輸出文件:
ffmpeg.exe -ic:/myvideo.mp4
似乎流1編解碼器的幀速率與容器的幀速率不同:59.92(14981/250)-> 29.96(14981/500)
從'c:/myvideo.mp4'輸入#0,mov,mp4,m4a,3gp,3g2,mj2:
元數據:
major_brand:mp42
minor_version:0
兼容品牌:isomavc1mp42
持續時間:00:04:20.96,開始:0.000000,比特率:582 kb / s
流#0.0(und):音頻:aac,44100 Hz,立體聲,s16、115 kb / s
流#0.1(und):視頻:h264,yuv420p,480×270 [PAR 1:1 DAR 16:9],464 kb / s,29.96 fps,29.96 tbr,29962 tbn,59.92 tbc
在原始視頻文件中,容器為MPEG-4 ,有兩個流:使用44100Hz的AAC的音頻流和使用H.264的視頻流。
ffmpeg.exe -ic:/myvideo.flv
似乎流0編解碼器的幀速率與容器的幀速率不同:1000.00(1000/1)-> 29.97(30000/1001)
從'c:/myvideo.flv'輸入#0 flv:
元數據:
持續時間:261
寬度:480
高度:270
videodatarate:62
幀率:30
videocodecid:2
音頻數據率:62
音頻采樣率:44100
音頻樣本大小:16
立體聲:真
音頻編解碼器:2
文件大小:43117478
持續時間:00:04:20.98,開始:0.000000,比特率:128 kb / s
流#0.0:視頻:flv,yuv420p,480×270、64 kb / s,29.97 tbr,1k tbn,1k tbc
流#0.1:音頻:mp3,44100 Hz,2聲道,s16,64 kb / s
轉碼后,生成的Flash視頻文件使用FLV視頻流和MP3音頻流。
現在,我們準備使用Xuggler修改媒體文件。 但是在編寫代碼之前,我們需要了解MediaTool的工作原理 :
MediaTool使用事件偵聽器范例。 寫入器會自動作為“偵聽器”添加到讀取器,并接收所有解碼的媒體。 IMediaViewer和IMediaWriter接口(查看器和編寫器實際上是什么)實現IMediaListener接口,并且可以作為偵聽器添加到IMediaReader。
我們通過前面的示例確認了這一點。 問題是,為了對輸入文件進行各種修改 ,我們必須建立一個“媒體管道”。 我們創建IMediaTool的自定義實現,然后以鏈式方式在每個工具上設置偵聽器,以便它們將數據從一個傳遞到另一個。
假設我們希望在視頻中添加靜態圖像,同時希望減少音頻量。 在這種情況下,我們將創建兩個自定義IMediaTool對象:
- StaticImageMediaTool:拍攝視頻圖片并在屏幕上的特定位置標記靜態圖像文件。
- VolumeAdjustMediaTool:按恒定因子調整音量。
另外,我們創建一個IMediaWriter對象,該對象將用于創建輸出文件。 通過所有這些,我們創建了一條鏈,如下所示:
讀取器-> addStaticImage-> reduceVolume->寫入器
讓我們看看實現以上所有功能的代碼:
package com.javacodegeeks.xuggler;import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ShortBuffer;import javax.imageio.ImageIO;import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.IMediaTool;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.MediaToolAdapter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.mediatool.event.IAudioSamplesEvent;
import com.xuggle.mediatool.event.IVideoPictureEvent;public class ModifyMediaExample {private static final String inputFilename = "c:/myvideo.mp4";private static final String outputFilename = "c:/myvideo.flv";private static final String imageFilename = "c:/jcg_logo_small.png";public static void main(String[] args) {// create a media readerIMediaReader mediaReader = ToolFactory.makeReader(inputFilename);// configure it to generate BufferImagesmediaReader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);IMediaWriter mediaWriter = ToolFactory.makeWriter(outputFilename, mediaReader);IMediaTool imageMediaTool = new StaticImageMediaTool(imageFilename);IMediaTool audioVolumeMediaTool = new VolumeAdjustMediaTool(0.1);// create a tool chain:// reader -> addStaticImage -> reduceVolume -> writermediaReader.addListener(imageMediaTool);imageMediaTool.addListener(audioVolumeMediaTool);audioVolumeMediaTool.addListener(mediaWriter);while (mediaReader.readPacket() == null) ;}private static class StaticImageMediaTool extends MediaToolAdapter {private BufferedImage logoImage;public StaticImageMediaTool(String imageFile) {try {logoImage = ImageIO.read(new File(imageFile));} catch (IOException e) {e.printStackTrace();throw new RuntimeException("Could not open file");}}@Overridepublic void onVideoPicture(IVideoPictureEvent event) {BufferedImage image = event.getImage();// get the graphics for the imageGraphics2D g = image.createGraphics();Rectangle2D bounds = new Rectangle2D.Float(0, 0, logoImage.getWidth(), logoImage.getHeight());// compute the amount to inset the time stamp and // translate the image to that positiondouble inset = bounds.getHeight();g.translate(inset, event.getImage().getHeight() - inset);g.setColor(Color.WHITE);g.fill(bounds);g.setColor(Color.BLACK);g.drawImage(logoImage, 0, 0, null);// call parent which will pass the video onto next tool in chainsuper.onVideoPicture(event);}}private static class VolumeAdjustMediaTool extends MediaToolAdapter {// the amount to adjust the volume byprivate double mVolume;public VolumeAdjustMediaTool(double volume) {mVolume = volume;}@Overridepublic void onAudioSamples(IAudioSamplesEvent event) {// get the raw audio bytes and adjust it's valueShortBuffer buffer = event.getAudioSamples().getByteBuffer().asShortBuffer();for (int i = 0; i < buffer.limit(); ++i) {buffer.put(i, (short) (buffer.get(i) * mVolume));}// call parent which will pass the audio onto next tool in chainsuper.onAudioSamples(event);}}}
與往常一樣,我們首先創建一個IMediaReader并在調用IMediaListener.onVideoPicture時使用setBufferedImageTypeToGenerate方法生成BufferedImage圖像。 為了將我們的自定義圖像覆蓋在實際的視頻圖片上,這是必要的。 然后,我們創建IMediaWriter和媒體工具對象,并如上所述配置工具鏈。 讓我們仔細看看自定義媒體工具。
首先,我們有StaticImageMediaTool類。 它擴展了MediaToolAdapter并覆蓋了onVideoPicture方法,因為我們希望使用這一方法來處理視頻流。 在構造函數中,我們已經使用ImageIO.read方法加載了一個圖像文件。 JavaCodeGeeks徽標用于此目的(實際上是其縮小版本)。 然后,在已實現的onVideoPicture方法中,我們通過調用IVideoPictureEvent.getImage獲得基礎的BufferedImage并創建一個Graphics2D對象。 然后,我們使用Graphics.drawImage方法覆蓋靜態圖像。 最后,我們調用父級onVideoPicture方法,該方法會將視頻傳遞到鏈中的下一個工具。
然后,我們有了VolumeAdjustMediaTool。 它還擴展了MediaToolAdapter ,但覆蓋了onAudioSamples方法,該方法在對音頻樣本進行解碼或編碼后調用。 在那里,我們通過調用IAudioSamplesEvent.getAudioSamples來獲取原始音頻字節,并使用相應的ShortBuffer類調整其值。 再次,在自定義處理之后,我們調用父onAudioSamples方法,該方法會將音頻傳遞到鏈中的下一個工具。 如果現在運行此應用程序,我們將在原始視頻的頂部看到添加的圖像,并且音頻音量將大大降低。
而已。 Xuggler支持的轉碼和媒體處理。 與往常一樣,您可以下載為本教程創建的Eclipse項目 。 請繼續關注JavaCodeGeeks上的更多Xuggler教程! 別忘了分享!
相關文章:
- Xuggler視頻處理簡介
- Xuggler教程:幀捕獲和視頻創建
- 使用wowza和xuggler將RTMP轉換為RTSP
翻譯自: https://www.javacodegeeks.com/2011/02/xuggler-tutorial-transcoding-media.html