国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

干貨 | 移動端使用OpenGL轉(zhuǎn)場特效的音視頻合成應用

這篇具有很好參考價值的文章主要介紹了干貨 | 移動端使用OpenGL轉(zhuǎn)場特效的音視頻合成應用。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

作者簡介

jzg,攜程資深前端開發(fā)工程師,專注Android開發(fā);

zx,攜程高級前端開發(fā)工程師,專注iOS開發(fā);

zcc,攜程資深前端開發(fā)工程師,專注iOS開發(fā)。

前言

近年來短視頻的火爆,讓內(nèi)容創(chuàng)作類的APP獲得了巨大的流量。用戶通過這類工具編輯自己的短視頻,添加各式各樣的炫酷特效,從而呈現(xiàn)出更加豐富多彩的視頻內(nèi)容。本文將會介紹如何使用移動端原生API,將圖片添加轉(zhuǎn)場特效并且最終合成為視頻的基本流程。

一、音視頻基礎知識

我們經(jīng)常會和視頻打交道,最常見的就是MP4格式的視頻。這樣的視頻其實一般是由音頻和視頻組成的音視頻容器。下面先會介紹音視頻相關(guān)概念,為音視頻技術(shù)的應用作一個鋪墊,希望能對音視頻頻開發(fā)者提供一些幫助。

1.1 視頻的基礎知識

1.1.1 視頻幀

視頻中的一個基本概念就是幀,幀用來表示一個畫面。視頻的連續(xù)畫面就是由一個個連續(xù)的視頻幀組成。

1.1.2? 幀率

幀率,F(xiàn)PS,全稱Frames Per Second。指每秒傳輸?shù)膸瑪?shù),或者每秒顯示的幀數(shù)。一般來說,幀率影響畫面流暢度,且成正比:幀率越大,畫面越流暢;幀率越小,畫面越有跳動感。一個較權(quán)威的說法:當視頻幀率不低于24FPS時,人眼才會覺得視頻是連貫的,稱為“視覺暫留”現(xiàn)象。16FPS可以達到一定的滿意程度,但效果略差。因此,才有說法:盡管幀率越高越流暢,但在很多實際應用場景中24FPS就可以了(電影標準24FPS,電視標準PAL制25FPS)。

1.1.3 分辨率

分辨率,Resolution,也常被俗稱為圖像的尺寸或者圖像的大小。指一幀圖像包含的像素的多少,常見有1280x720(720P),1920X1080(1080P)等規(guī)格。分辨率影響圖像大小,且與之成正比:分辨率越高,圖像越大;反之,圖像越小。

1.1.4 碼率

碼率,BPS,全稱Bits Per Second。指每秒傳送的數(shù)據(jù)位數(shù),常見單位KBPS(千位每秒)和MBPS(兆位每秒)。碼率是更廣泛的(視頻)質(zhì)量指標:更高的分辨率,更高的幀率和更低的壓縮率,都會導致碼率增加。

1.1.5 色彩空間

通常說的色彩空間有兩種:

RGB:RGB的顏色模式應該是我們最熟悉的一種,在現(xiàn)在的電子設備中應用廣泛。通過R、G、B三種基礎色,可以混合出所有的顏色。

YUV:YUV是一種亮度與色度分離的色彩格式,三個字母的意義分別為:

Y:亮度,就是灰度值。除了表示亮度信號外,還含有較多的綠色通道量。單純的Y分量可以顯示出完整的黑白圖像。

U:藍色通道與亮度的差值。

V:紅色通道與亮度的差值。

其中,U、V分量分別表示藍(blue)、紅(red)分量信號,只含有色度信息,所以YUV也稱為YCbCr,其中,Cb、Cr的含義等同于U、V,C可以理解為component或者color。

RGB和YUV的換算

YUV與RGB相互轉(zhuǎn)換的公式如下(RGB取值范圍均為0-255):

Y = 0.299R + 0.587G + 0.114BU = -0.147R - 0.289G + 0.436BV = 0.615R - 0.515G - 0.100B R = Y + 1.14VG = Y - 0.39U - 0.58VB = Y + 2.03U

1.2 音頻的基礎知識

音頻數(shù)據(jù)的承載方式最常用的是脈沖編碼調(diào)制,即PCM。

1.2.1 采樣率和采樣位數(shù)

采樣率是將聲音進行數(shù)字化的采樣頻率,采樣位數(shù)與記錄聲波振幅有關(guān),位數(shù)越高,記錄的就越準確。

1.2.2 聲道數(shù)

聲道數(shù),是指支持能不同發(fā)聲(注意是不同聲音)的音響的個數(shù)。

1.2.3 碼率

碼率,是指一個數(shù)據(jù)流中每秒鐘能通過的信息量,單位bps(bit per second)。

碼率 = 采樣率 * 采樣位數(shù) * 聲道數(shù)

上面介紹的音視頻的數(shù)據(jù)還需要進行壓縮編碼,因為音視頻的數(shù)據(jù)量都非常大,按照原始數(shù)據(jù)保存會非常的耗費空間,而且想要傳輸這樣龐大的數(shù)據(jù)也很不方便。其實音視頻的原始數(shù)據(jù)中包含大量的重復數(shù)據(jù),特別是視頻,一幀一幀的畫面中包含大量的相似的內(nèi)容。所以需要對音視頻數(shù)據(jù)進行編碼,以便于減小占用的空間,提高傳輸?shù)男省?br>

1.3 視頻編碼

通俗地理解,例如一個視頻中,前一秒畫面跟當前的畫面內(nèi)容相似度很高,那么這兩秒的數(shù)據(jù)是不是可以不用全部保存,只保留一個完整的畫面,下一個畫面看有哪些地方有變化了記錄下來,拿視頻去播放的時候就按這個完整的畫面和其他有變化的地方把其他畫面也恢復出來。記錄畫面不同然后保存下來這個過程就是數(shù)據(jù)編碼,根據(jù)不同的地方恢復畫面的過程就是數(shù)據(jù)解碼。

一般常見的視頻編碼格式有H26x系列和MPEG系列。
H26x(1/2/3/4/5)系列由ITU(International Telecommunication Union)國際電傳視訊聯(lián)盟主導。
MPEG(1/2/3/4)系列由MPEG(Moving Picture Experts Group, ISO旗下的組織)主導。

H264是新一代的編碼標準,以高壓縮高質(zhì)量和支持多種網(wǎng)絡的流媒體傳輸著稱。iOS 8.0及以上蘋果開放了VideoToolbox框架來實現(xiàn)H264硬編碼,開發(fā)者可以利用VideoToolbox框架很方便地實現(xiàn)視頻的硬編碼。

H264編碼的優(yōu)勢:

  • 低碼率

  • 高質(zhì)量的圖像?

  • 容錯能力強

  • 網(wǎng)絡適應性強

H264最大的優(yōu)勢,具有很高的數(shù)據(jù)壓縮比率,在同等圖像質(zhì)量下,H264的壓縮比是MPEG-2的2倍以上,MPEG-4的1.5~2倍。舉例: 原始文件的大小如果為88GB,采用MPEG-2壓縮標準壓縮后變成3.5GB,壓縮比為25∶1,而采用H.264壓縮標準壓縮后變?yōu)?79MB,從88GB到879MB,H.264的壓縮比達到驚人的102∶1。

1.4 音頻編碼

和視頻編碼一樣,音頻也有許多的編碼格式,如:WAV、MP3、WMA、APE、FLAC等等。

AAC

  • AAC是目前比較熱門的有損壓縮編碼技術(shù),并且衍生了LC-AAC,HE-AAC,HE-AAC v2 三種主要編碼格式

  • 特點:在小于128Kbit/s的碼率下表現(xiàn)優(yōu)異,并且多用于視頻中的音頻編碼

  • 使用場合:128Kbit/s以下的音頻編碼,多用于視頻中音頻軌的編碼

WAV

  • 在PCM數(shù)據(jù)格式的前面加上44字節(jié),描述PCM的采樣率、聲道數(shù)、數(shù)據(jù)格式等信息,不會壓縮

  • 特點:音質(zhì)好,大量軟件支持

  • 使用場合:多媒體開發(fā)的中間文件、保存音樂和音效素材

MP3

  • 使用LAME編碼

  • 特點:音質(zhì)在128kbit/s以上表現(xiàn)不錯,壓縮比較高,大量軟件硬件都支持,兼容性好

  • 使用場合:高比特率(傳輸效率 bps, 這里的b是位,不是比特)對兼容性有要求的音樂欣賞

OGG

  • 特點:可以用比MP3更小的碼率實現(xiàn)比MP3更好的音質(zhì),高中低碼率下均有良好的表現(xiàn)

  • 不足:兼容性不夠好,流媒體特性不支持

  • 適合場景:語音聊天的音頻消息場景

APE

  • 無損壓縮

FLAC

  • 專門針對PCM音頻的特點設計的壓縮方式,而且可以使用播放器直接播放FLAC壓縮的文件

  • 免費,支持大多數(shù)操作系統(tǒng)

二、使用OpenGL的底層轉(zhuǎn)場特效和原生平臺硬編碼進行圖片、音樂、轉(zhuǎn)場合成視頻需要哪些 API

2.1 Android端和使用流程及相關(guān)API介紹

如果想要給圖片添加轉(zhuǎn)場特效并且合成為視頻,需要使用OpenGL對圖片進行渲染,搭配自定義的轉(zhuǎn)場著色器,先讓圖片"動起來"。然后使用MediaCodec將畫面內(nèi)容進行編碼,然后使用MediaMuxer將編碼后的內(nèi)容打包成一個音視頻容器文件。

2.1.1 Mediacodec

MediaCodec是從API16后引入的處理音視頻編解碼的類,它可以直接訪問Android底層的多媒體編解碼器,通常與MediaExtractor,MediaSync, MediaMuxer,MediaCrypto,MediaDrm,Image,Surface,以及AudioTrack一起使用。

下面是官網(wǎng)提供的MediaCodec工作的流程圖:

trackswithmediatype,音視頻

我們可以看到左邊是input,右邊是output。這里要分兩種情況來討論:

1)利用MediaCodec進行解碼的時候,輸入input是待解碼的buffer數(shù)據(jù),輸出output是解碼好的buffer數(shù)據(jù)。

2)利用MediaCodec進行編碼的時候,輸入input是一個待編碼的數(shù)據(jù),輸出output是編碼好的buffer數(shù)據(jù)。

val width = 720
    val height = 1280
    val bitrate = 5000
    val encodeType = "video/avc"
    //配置用于編碼的MediaCodec
    val mCodec = MediaCodec.createEncoderByType(encodeType)
    val outputFormat = MediaFormat.createVideoFormat(encodeType, width, height)
    outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
    outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, DEFAULT_ENCODE_FRAME_RATE)
    outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
    outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        outputFormat.setInteger(
            MediaFormat.KEY_BITRATE_MODE,
            MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ
        )
    }
    codec.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    //這一步很關(guān)鍵,這一步得到的surface后面將會用到
    val mSurface = codec.createInputSurface()
    mCodec.start()
    val mOutputBuffers = mCodec.outputBuffers
    val mInputBuffers = mCodec.inputBuffers

以上是MediaCodec的作為編碼器的基本配置,其中MediaCodec.createInputSurface()這個方法可以為我們創(chuàng)建一個用于向MediaCodec進行輸入的surface。這樣通過MediaCodec就能獲取到編碼后的數(shù)據(jù)了。用這樣的方式編碼我們不需要向MedaiCodec輸入待編碼的數(shù)據(jù),MediaCodec會自動將輸入到surface的數(shù)據(jù)進行編碼。

2.1.2 EGL環(huán)境

OpenGL是一組用來操作GPU的API,但它并不能將繪制的內(nèi)容渲染到設備的窗口上,這里需要一個中間層,用來作為OpenGL和設備窗口之間的橋梁,并且最好是跨平臺的,這就是EGL,是由Khronos Group提供的一組平臺無關(guān)的API。

OpenGL繪制的內(nèi)容一般都是呈現(xiàn)在GLSurfaceView中的(GLSurfaceView的surface),如果我們需要將內(nèi)容編碼成視頻,需要將繪制的內(nèi)容渲染到MediaCodec提供的Surface中,然后獲取MediaCodec輸出的編碼后的數(shù)據(jù),封裝到指定的音視頻文件中。

創(chuàng)建EGL環(huán)境的主要步驟如下:

//1,創(chuàng)建 EGLDisplay
val mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
// 2,初始化 EGLDisplay
val version = IntArray(2)
EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)
// 3,初始化EGLConfig,EGLContext上下文
val config :EGLConfig? = null
if (mEGLContext === EGL14.EGL_NO_CONTEXT) {
    var renderableType = EGL14.EGL_OPENGL_ES2_BIT
        val attrList = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_ALPHA_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, renderableType,
            EGL14.EGL_NONE, 0,
            EGL14.EGL_NONE
        )
        //配置Android指定的標記
        if (flags and FLAG_RECORDABLE != 0) {
            attrList[attrList.size - 3] = EGL_RECORDABLE_ANDROID
            attrList[attrList.size - 2] = 1
        }
        val configs = arrayOfNulls<EGLConfig>(1)
        val numConfigs = IntArray(1)


        //獲取可用的EGL配置列表
        if (!EGL14.eglChooseConfig(mEGLDisplay, attrList, 0,
                configs, 0, configs.size,
                numConfigs, 0)) {
            configs[0]
        }
    val attr2List = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
    val context = EGL14.eglCreateContext(
        mEGLDisplay, config, sharedContext,
        attr2List, 0
    )
    mEGLConfig = config
    mEGLContext = context
}
//這里還需要創(chuàng)建一個EGL用于輸出的surface,這里的參數(shù)就可以傳入上一小節(jié)介紹的利用MeddiaCodec創(chuàng)建的Surface
 fun createWindowSurface(surface: Any): EGLSurface {
        val surfaceAttr = intArrayOf(EGL14.EGL_NONE)


        val eglSurface = EGL14.eglCreateWindowSurface(
                                        mEGLDisplay, mEGLConfig, surface,
                                        surfaceAttr, 0)


        if (eglSurface == null) {
            throw RuntimeException("Surface was null")
        }


        return eglSurface
    }

配置EGL環(huán)境后,還要一個surface作為輸出,這里就是要利用MediaCodec創(chuàng)建的surface作為輸出,即EGL的輸出作為MediaCodec的輸入。

2.1.3 MediaMuxer

MediaMuxer是Android平臺的音視頻合成工具,上面我們介紹了MediaCodec可以編碼數(shù)據(jù),EGL環(huán)境可以讓OpenGL程序?qū)⒗L制的內(nèi)容渲染到MediaCodec中,MediaCodec將這些數(shù)據(jù)編碼,最后這些編碼后的數(shù)據(jù)需要使用MediaMuxer寫入到指定的文件中。

MediaMuxer基本使用:

//創(chuàng)建一個MediaMuxer,需要指定輸出保存的路徑,和輸出保存的格式。
val mediaMuxer = MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
//根據(jù)MediaFormat添加媒體軌道
mediaMuxer.addTrack(MediaFormat(...))
//將輸入的數(shù)據(jù),根據(jù)指定的軌道保存到指定的文件路徑中。
muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo);
//結(jié)合上面的所說的使用MediaCodec獲取到已編碼的數(shù)據(jù)
//當前幀的信息
var mBufferInfo = MediaCodec.BufferInfo()
//編碼輸出緩沖區(qū)
var mOutputBuffers: Array<ByteBuffer>? = null
//獲取到編碼輸出的索引,寫到指定的保存路徑
val index = mCodec.dequeueOutputBuffer(mBufferInfo, 1000)
muxer.writeSampleData(currentTrackIndex,mOutputBuffers[index],mBufferInfo)

2.1.4 MediaExtractor

MediaExtractor是Android平臺的多媒體提取器,能夠根據(jù)視頻軌道或者音頻軌道去提取對應的數(shù)據(jù)。在進行視頻編輯時,可以利用MediaExtractor來提取指定的音頻信息,封裝到目標音視頻文件中。

//根據(jù)指定文件路徑創(chuàng)建MediaExtractor
 val mediaExtractor = MediaExtractor(...)
 //為MediaExtractor選擇好對應的媒體軌道
 mediaExtractor.selectTrack(...)
 //讀取一幀的數(shù)據(jù)
 val inputBuffer = ByteBuffer.allocate(...)
 mediaExtractor.readSampleData(inputBuffer, 0)
 //進入下一幀
 mediaExtractor.advance()
 //MediaExtractor讀取到的音頻數(shù)據(jù)可以使用MediaMuxer的writeSampleData方法寫入到指定的文件中

以上就是利用Android平臺的硬編碼相關(guān)API,將OpenGL渲染到畫面編碼成視頻的基本流程介紹。

三、iOS端合成流程及相關(guān)API使用

由于AVFoundation原生框架對于圖層特效處理能力有限,無法直接生成和寫入多張圖片之間切換的轉(zhuǎn)場效果,所以需要自行對圖片和音樂按照時間線,去實現(xiàn)音視頻數(shù)據(jù)從解碼到轉(zhuǎn)場特效應用,以及最終寫入文件的整個流程。

那么在多張圖片合成視頻的過程中,核心的部分就是如何處理多張圖片之間的轉(zhuǎn)場效果。這個時候我們需要配合OpenGL底層的特效能力,自定義濾鏡將即將要切換的2張圖片通過片元著色器生成新的紋理。本質(zhì)就是在這兩個紋理對象上去實現(xiàn)紋理和紋理之間的切換,通過Mix函數(shù)混合兩個紋理圖像,使用time在[0,1]之間不停變化來控制第二個圖片紋理混合的強弱變化從而實現(xiàn)漸變效果。接下來開始介紹合成的流程和具體API的使用。

3.1 音視頻基礎API

在合成的過程中,我們使用到了AVAssetWriter這個類。AVAssetWriter可以將多媒體數(shù)據(jù)從多個源進行編碼(比如接下來的多張圖片和一個BGM進行合成)并寫入指定文件格式的容器中,比如我們熟知的MPEG-4文件。

3.1.1 AVAssetWriter 與AVAssetWriterInput

AVAssetWriter通常由一個或多個AVAssetWriterInput對象構(gòu)成,將AVAssetWriterInput配置為可以處理指定的多媒體類型,比如音頻或視頻,用于添加將包含要寫入容器的多媒體數(shù)據(jù)的CMSampleBufferRef對象。同時因為asset writer可以從多個數(shù)據(jù)源寫入容器,因此必須要為寫入文件的每個track(即音頻軌道、視頻軌道)創(chuàng)建一個對應的AVAssetWriterInput對象。

AVAssetWriterInput可以設置視頻的主要參數(shù)如輸出碼率,幀率,最大幀間隔,編碼方式,輸出分辨率以及填充模式等。也可以設置音頻的主要參數(shù)如采樣率,聲道,編碼方式,輸出碼率等。

3.1.2 CMSampleBufferRef 與AVAssetWriterInputPixelBufferAdaptor

CMSampleBuffer是一個基礎類,用于處理音視頻管道傳輸中的通用數(shù)據(jù)。CMSampleBuffer中包含零個或多個某一類型如音頻或者視頻的采樣數(shù)據(jù)。可以封裝音頻采集后、編碼后、解碼后的數(shù)據(jù)(PCM數(shù)據(jù)、AAC數(shù)據(jù))以及視頻編碼后的數(shù)據(jù)(H.264數(shù)據(jù))。而CMSampleBufferRef是對CMSampleBuffer的一種引用。在提取音頻的時候,像如下的使用方式同步復制輸出的下一個示例緩沖區(qū)。

CMSampleBufferRef?sampleBuffer?=?[assetReaderAudioOutput?copyNextSampleBuffer];

每個AVAssetWriterInput期望以CMSampleBufferRef對象形式接收數(shù)據(jù),如果在處理視頻樣本的數(shù)據(jù)時,便要將CVPixelBufferRef類型對象(像素緩沖樣本數(shù)據(jù))添加到asset writer input,這個時候就需要使用AVAssetWriterInputPixelBufferAdaptor 這個專門的適配器類。這個類在附加被包裝為CVPixelBufferRef對象的視頻樣本時提供最佳性能。

AVAssetWriterInputPixelBufferAdaptor它是一個輸入的像素緩沖適配器,作為assetWriter的視頻輸入源,用于把緩沖池中的像素打包追加到視頻樣本上。在寫入文件的時候,需要將CMSampleBufferRef轉(zhuǎn)成CVPixelBuffer,而這個轉(zhuǎn)換是在CVPixelBufferPool中完成的。AVAssetWriterInputPixelBufferAdaptor的實例提供了一個CVPixelBufferPool,可用于分配像素緩沖區(qū)來寫入輸出數(shù)據(jù)。使用它提供的像素緩沖池進行緩沖區(qū)分配通常比使用額外創(chuàng)建的緩沖區(qū)更加高效。

CVPixelBufferRef pixelBuffer = NULL;
    CVPixelBufferPoolCreatePixelBuffer(NULL, self.inputPixelBufferAdptor.pixelBufferPool,&pixelBuffer);

每個AVAssetWriterInputPixelBufferAdaptor都包含一個assetWriterInput,用于接收緩沖區(qū)中的數(shù)據(jù),并且AVAssetWriterInput有一個很重要的屬性readyForMoreMediaData,來標識現(xiàn)在緩沖區(qū)中的數(shù)據(jù)是否已經(jīng)處理完成。通過判斷這個屬性,我們可以向AVAssetWriterInputPixelBufferAdaptor中添加數(shù)據(jù)(appendPixelBuffer:)以進行處理。

if(self.inputPixelBufferAdptor.assetWriterInput.isReadyForMoreMediaData) {
????BOOL?success?=?[self.inputPixelBufferAdptor?appendPixelBuffer:newPixelBuffer?withPresentationTime:self.currentSampleTime];????


    if (success) {
        NSLog(@"append buffer success");
    }
}

3.1.3 設置輸入輸出參數(shù),以及多媒體數(shù)據(jù)的采樣

第一步:創(chuàng)建AVAssetWriter對象,傳入生成視頻的路徑和格式

AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:outFilePath] fileType:AVFileTypeMPEG4 error:&outError];

第二步:設置輸出視頻相關(guān)信息,如大小,編碼格式H264,以及創(chuàng)建視頻的輸入類videoWriterInput,以便后續(xù)給assetReader添加videoWriterInput。

CGSize size = CGSizeMake(480, 960);


NSDictionary?*videoSetDic?=?[NSDictionary?dictionaryWithObjectsAndKeys:AVVideoCodecTypeH264,AVVideoCodecKey,
[NSNumber numberWithInt:size.width],AVVideoWidthKey,[NSNumber numberWithInt:size.height],AVVideoHeightKey,nil];


AVAssetWriterInput?*videoWriterInput?=?[[AVAssetWriterInput?alloc]?initWithMediaType:AVMediaTypeVideo?outputSettings:videoSetDic];


//將讀取的圖片內(nèi)容添加到assetWriter??????????????????????????????????????????????
if ([assetWriter canAddInput:videoWriterInput]) {
    [assetWriter addInput:videoWriterInput];
}

第三步:創(chuàng)建一個處理視頻樣本時專用的適配器對象,這個類在附加被包裝為CVPixelBufferRef對象的視頻樣本時能提供最優(yōu)性能。如果想要將CVPixelBufferRef類型對象添加到asset writer input,就需要使用AVAssetWriterInputPixelBufferAdaptor類。

NSDictionary *pixelBufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA],kCVPixelBufferPixelFormatTypeKey,nil];


AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput pixelBufferAttributes:pixelBufferAttributes];

第四步:音頻數(shù)據(jù)的采集、添加音頻輸入

//創(chuàng)建音頻資源
AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:audioUrl options:nil];
//創(chuàng)建音頻Track
AVAssetTrack *assetAudioTrack = [audioAsset tracksWithMediaType:AVMediaTypeAudio].firstObject;
//創(chuàng)建讀取器 
AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:audioAsset error:&error];
//讀取音頻track中的數(shù)據(jù)
NSDictionary *audioSettings = @{AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM]};


AVAssetReaderTrackOutput *assetReaderAudioOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:assetAudioTrack outputSettings: audioSettings];
//向接收器添加assetReaderAudioOutput輸出
if ([assetReader canAddOutput:assetReaderAudioOutput]) {
    [assetReader addOutput:assetReaderAudioOutput];
}


//音頻通道數(shù)據(jù),設置音頻的比特率、采樣率的通道數(shù)
AudioChannelLayout acl;
bzero( &acl, sizeof(acl));
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;


NSData *channelLayoutAsData = [NSData dataWithBytes:&acl length:offsetof(AudioChannelLayout, acl)];


NSDictionary?*audioSettings?=?@{AVFormatIDKey:[NSNumber?numberWithUnsignedInt:kAudioFormatMPEG4AAC],AVEncoderBitRateKey:[NSNumber?numberWithInteger:128000],?AVSampleRateKey:[NSNumber?numberWithInteger:44100],?AVChannelLayoutKey:channelLayoutAsData,AVNumberOfChannelsKey?:?[NSNumber?numberWithUnsignedInteger:2]};
//創(chuàng)建音頻的assetWriterAudioInput,將讀取的音頻內(nèi)容添加到assetWriter
AVAssetWriterInput *assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:[assetAudioTrack mediaType] outputSettings: audioSettings];


if ([assetWriter canAddInput:assetWriterAudioInput]) {
    [assetWriter addInput:assetWriterAudioInput];
}


//Writer開始進行寫入流程
[assetWriter startSessionAtSourceTime:kCMTimeZero];

3.2 轉(zhuǎn)場切換效果中的圖片處理

上面介紹了音視頻合成的大致流程,但是核心的部分是在于我們在合成視頻時,如何去寫入第一張和第二張圖片展示間隙中的切換過程效果。這個時候就得引入GPUImage這個底層框架,而GPUImage是iOS端對OpenGL的封裝。即我們通過繼承GPUImageFilter去實現(xiàn)自定義濾鏡,并重寫片元著色器的效果,通過如下代理回調(diào)得到這個過程中返回的一系列處理好的紋理樣本數(shù)據(jù)。

-(void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;

然后轉(zhuǎn)換成相應的pixelBuffer數(shù)據(jù),通過調(diào)用appendPixelBuffer:添加到幀緩存中去,從而寫入到文件中。

-(BOOL)appendPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime;

3.2.1 如何自定義濾鏡

在GPUImageFilter中默認的著色器程序比較簡單,只是簡單的進行紋理采樣,并沒有對像素數(shù)據(jù)進行相關(guān)操作。所以在自定義相關(guān)濾鏡的時候,我們通常需要自定義片段著色器的效果來處理紋理效果從而達到豐富的轉(zhuǎn)場效果。

我們通過繼承GPUImageFilter來自定義我們轉(zhuǎn)場效果所需的濾鏡,首先是創(chuàng)建一個濾鏡文件compositeImageFilter繼承于GPUImageFilter,然后重寫父類的方法去初始化頂點和片段著色器。

- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;

這個時候需要傳入所需的片元著色器代碼,那么怎么自定義GLSL文件呢,以下便是如何編寫具體的GLSL文件,即片元著色器實現(xiàn)代碼。傳入紋理的頂點坐標textureCoordinate、2張圖片的紋理imageTexture、imageTexture2,通過mix函數(shù)混合兩個紋理圖像,使用time在[0,1]之間不停變化來控制第二個圖片紋理混合的強弱變化從而實現(xiàn)漸變效果。

precision highp float;
varying highp vec2 textureCoordinate;
uniform sampler2D imageTexture;
uniform sampler2D imageTexture2;
uniform mediump vec4 v4Param1;
float progress = v4Param1.x;
void main()
{
    vec4 color1 = texture2D(imageTexture, textureCoordinate);
    vec4 color2 = texture2D(imageTextur2, textureCoordinate);
    gl_FragColor = mix(color1, color2, step(1.0-textureCoordinate.x,progress));
}

3.2.2 了解GPUImageFilter中重點API

在GPUImageFilter中有三個最重要的API,GPUImageFilter會將接收到的幀緩存對象經(jīng)過特定的片段著色器繪制到即將輸出的幀緩存對象中,然后將自己輸出的幀緩存對象傳給所有Targets并通知它們進行處理。方法被調(diào)用的順序:

1)生成新的幀緩存對象

- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;

2)進行紋理的繪制??

- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;

3)繪制完成通知所有的target處理下一幀的紋理數(shù)據(jù)?

- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime;

通過如上代理回調(diào)就可以得到這個過程中返回的一系列處理好的紋理樣本數(shù)據(jù)。

按照方法調(diào)用順序,我們一般先重寫newFrameReadyAtTime方法,構(gòu)建最新的頂點坐標,生成新的幀緩存對象。

- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex {
    static const GLfloat imageVertices[] = {
        -1.0f, -1.0f,
        1.0f, -1.0f,
        -1.0f,  1.0f,
        1.0f,  1.0f,
    };
    [self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
}

然后在這個方法中調(diào)用renderToTextureWithVertices去繪制所需的紋理,并獲取到最終的幀緩存對象。以下是部分核心代碼:

- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates {
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE5);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 5);
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, [self adjustVertices:vertices]);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFinish();
CVPixelBufferRef pixel_buffer = NULL;
CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, [self.videoPixelBufferAdaptor pixelBufferPool], &pixel_buffer);
if ((pixel_buffer == NULL) || (status != kCVReturnSuccess)) {
    CVPixelBufferRelease(pixel_buffer);
    return;
} else {
    CVPixelBufferLockBaseAddress(pixel_buffer, 0);
    GLubyte *pixelBufferData = (GLubyte *)CVPixelBufferGetBaseAddress(pixel_buffer);
    glReadPixels(0, 0, self.sizeOfFBO.width, self.sizeOfFBO.height, GL_RGBA, GL_UNSIGNED_BYTE, pixelBufferData);
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);
        }
    }
}

3.2.3 pixel_buffer的寫入

在上述的處理過程當中,我們便獲取到了所需的幀緩存樣本數(shù)據(jù)pixel_buffer。而這個數(shù)據(jù)便是合成轉(zhuǎn)場切換過程中的數(shù)據(jù),我們把它進行寫入,自此便完成了第一張和第二張圖片轉(zhuǎn)場效果效果的寫入。待轉(zhuǎn)場效果寫入之后,我們便可按照此流程根據(jù)時間的進度寫入第二張圖片以及后續(xù)的第二張圖片和第三張圖片的轉(zhuǎn)場效果。依此類推,一直到寫完所有的圖片。

CVPixelBufferLockBaseAddress(pixelBuffer, 0);
if (self.assetWriter.status != AVAssetWriterStatusWriting) {
    [self.assetWriter startWriting];
}
[self.assetWriter startSessionAtSourceTime:frameTime];
if (self.assetWriter.status == AVAssetWriterStatusWriting) {
    if (CMTIME_IS_NUMERIC(frameTime) == NO)  {
        CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
        return;
    }
    //確定寫操作是否已完成、失敗或已取消
????if?([self.videoPixelBufferAdaptor?appendPixelBuffer:pixelBufferwithPresentationTime:frameTime])?{????????????????????????????????????????????????????
        NSLog(@"%f", CMTimeGetSeconds(frameTime));
    }
}else {
    NSLog(@"status:%d", self.assetWriter.status);
    }
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

以上便是在iOS端處理音視頻合成的具體步驟,難點在于如何使用GPUImage去實現(xiàn)復雜的轉(zhuǎn)場效果并將其寫到到容器中。

本文介紹了音視頻相關(guān)的基本知識,讓大家對音視頻的關(guān)鍵概念有了一些理解。然后分別介紹了Android和iOS這兩個移動平臺音視頻編解碼API,利用這些平臺自帶的API,我們可以將OpenGL渲染的畫面編碼成音視頻文件。鑒于篇幅限制,文中的流程只截取了部分關(guān)鍵步驟的代碼,歡迎大家來交流音視頻相關(guān)的知識。

【推薦閱讀】

  • 瘦身50%-70%,攜程 Taro 小程序樣式 Size 縮減方案

  • Flutter 地圖在攜程的最佳實踐

  • 攜程火車票iOS項目開發(fā)體驗優(yōu)化實踐

  • 攜程機票App KMM iOS工程配置實踐

trackswithmediatype,音視頻

?“攜程技術(shù)”公眾號

? 分享,交流,成長文章來源地址http://www.zghlxwxcb.cn/news/detail-766760.html

到了這里,關(guān)于干貨 | 移動端使用OpenGL轉(zhuǎn)場特效的音視頻合成應用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關(guān)文章

  • 音視頻-SDL的簡單使用

    SDL(Simple DirectMedia Layer)是一套開放源代碼的跨平臺多媒體開發(fā)庫,使用C語言寫成。SDL提供了數(shù)種控制圖像、聲音、輸出入的函數(shù),讓開發(fā)者只要用相同或是相似的代碼就可以開發(fā)出跨多個平臺(Linux、Windows、Mac OS X等)的應用軟件。目前SDL多用于開發(fā)游戲、模擬器、媒體播

    2024年01月19日
    瀏覽(24)
  • 使用Qt進行音視頻播放

    使用Qt進行音視頻播放

    ??Qt對音視頻的播放和控制,相機拍照,收音機等多媒體應用提供了強大的支持。Qt5使用了全新的Qt Multimedia模塊來實現(xiàn)多媒體應用,而原來Qt4中用于實現(xiàn)多媒體功能的Phonon模塊已經(jīng)被移除。 ??新的Qt Multimedia模塊提供了豐富的接口,使讀者可以輕松地使用平臺的多媒體功

    2024年02月03日
    瀏覽(21)
  • 使用Python采集某網(wǎng)站視頻,實現(xiàn)音視頻自動合成!

    使用Python采集某網(wǎng)站視頻,實現(xiàn)音視頻自動合成!

    ? 開發(fā)環(huán)境 模塊的使用 采集下破站視頻數(shù)據(jù) 通過開發(fā)者工具進行抓包分析,分析破站視頻數(shù)據(jù)的來源。 開發(fā)者工具的使用 打開方式: 鼠標右鍵點擊檢查選擇Network F12 ctrl + shift + i 想要開發(fā)者工具中有數(shù)據(jù)加載, 需要 刷新網(wǎng)頁。 通過元素(Element)面板,我們能查看到想抓取

    2024年02月06日
    瀏覽(15)
  • iOS使用AVCaptureSession實現(xiàn)音視頻采集

    AVCaptureSession配置采集行為并協(xié)調(diào)從輸入設備到采集輸出的數(shù)據(jù)流。要執(zhí)行實時音視頻采集,需要實例化采集會話并添加適當?shù)妮斎牒洼敵觥?AVCaptureSession:管理輸入輸出音視頻流 AVCaptureDevice:相機硬件的接口,用于控制硬件特性,諸如鏡頭的位置(前后攝像頭)、曝光、閃光燈

    2024年02月06日
    瀏覽(20)
  • WebRTC音視頻通話-WebRTC本地視頻通話使用ossrs服務搭建

    WebRTC音視頻通話-WebRTC本地視頻通話使用ossrs服務搭建

    iOS開發(fā)-ossrs服務WebRTC本地視頻通話服務搭建 之前開發(fā)中使用到了ossrs,這里記錄一下ossrs支持的WebRTC本地服務搭建。 ossrs是什么呢? SRS(Simple Realtime Server)是一個簡單高效的實時視頻服務器,支持RTMP、WebRTC、HLS、HTTP-FLV、SRT等多種實時流媒體協(xié)議。 官網(wǎng)地址:https://ossrs.net/lt

    2024年02月12日
    瀏覽(22)
  • gstreamer中使用webrtc實現(xiàn)音視頻對講

    gstreamer中使用webrtc實現(xiàn)音視頻對講

    gstreamer官方源代碼中有一個基于webrtc插件實現(xiàn)音視頻通話的開源項目,下面介紹在Ubuntu系統(tǒng)中如何搭建環(huán)境并使用。 這里省略gstreamer安裝,直接安裝使用webrtcbin插件使用的相關(guān)庫,參考官網(wǎng)。系統(tǒng)版本建議高于ubuntu18.04。 首先安裝如下相關(guān)依賴庫。 gstreamer項目編譯官方建議

    2024年04月11日
    瀏覽(29)
  • 視頻批量混剪剪輯軟件類似剪映設計一個模板后, 視頻,圖片,文字,轉(zhuǎn)場,音頻,特效都可以系統(tǒng)隨機

    隨著自媒體時代的到來,越來越多的人加入到了視頻創(chuàng)作行列。然而,視頻剪輯是一項繁瑣的任務,特別是當你需要批量處理多個視頻時。為了提高效率,一款名為“視頻閃閃”的批量剪輯軟件應運而生。 www.shipinshanshan.com “視頻閃閃”具備多種功能,如視頻分割、合并、轉(zhuǎn)

    2024年02月05日
    瀏覽(31)
  • 使用Sora部署實時音視頻通信應用實戰(zhàn)項目

    使用Sora部署實時音視頻通信應用實戰(zhàn)項目

    ??????? ? 本項目將構(gòu)建一個在線教學平臺,實現(xiàn)教師與學生之間的實時音視頻通信。 平臺將提供教師上傳課件、發(fā)起授課邀請,學生加入課堂、實時互動等功能。通過使用 Sora ,我們將確保音視頻通信的穩(wěn)定、流暢和低延遲。 目錄 一、項目概述 二、準備工作 三、集成

    2024年02月20日
    瀏覽(27)
  • 如何使用Python進行可視化/音視頻處理?

    要使用Python進行可視化和音視頻處理,可以使用以下庫: matplotlib:用于繪制各種類型的圖表和圖形,包括折線圖、柱狀圖、散點圖等。 seaborn:基于matplotlib的可視化庫,提供更高級別的圖表和樣式,用于創(chuàng)建各種吸引人的統(tǒng)計圖表。 plotly:用于創(chuàng)建交互式圖表和數(shù)據(jù)可視化

    2024年02月09日
    瀏覽(32)
  • 5G時代音視頻開發(fā)前景怎么樣?音視頻開發(fā)需要掌握哪些技術(shù)?(1),手把手教你5G時代Webview的正確使用姿勢

    5G時代音視頻開發(fā)前景怎么樣?音視頻開發(fā)需要掌握哪些技術(shù)?(1),手把手教你5G時代Webview的正確使用姿勢

    你好! 這是你第一次使用 Markdown編輯器 所展示的歡迎頁。如果你想學習如何使用Markdown編輯器, 可以仔細閱讀這篇文章,了解一下Markdown的基本語法知識。 我們對Markdown編輯器進行了一些功能拓展與語法支持,除了標準的Markdown編輯器功能,我們增加了如下幾點新功能,幫助你

    2024年04月11日
    瀏覽(29)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包