天天看點

EasyPlayer Android音頻解碼庫(第二部分,封裝解碼器接口)

上一節我們講了如何基于ffmpeg-android工程編譯安卓上的支援音頻的ffmpeg靜态庫,這篇文章我們将介紹如何封裝安卓的解碼器。

首先,為了能讓我們的app調用調用,我們需要定義一套Java的native接口,解碼器主要有三個接口:

  • create 建立解碼器,參數分别為:
    • codec:音頻編碼格式,參考EasyTypes.h裡面的EASY_SDK_AUDIO_CODEC_*宏定義;
    • sample_rate:采樣率,通常為8000、44000等等;
    • channels:通道數,1、2分别表示單通道、雙通道;
    • sample_bit:采樣精度,通常為16bit;

      傳回解碼器句柄,即後續接口裡面用到的handle

  • decode 解碼,參數為:
    • handle :解碼器句柄
    • buffer:要解碼的buffer(編碼後的音頻資料)
    • offset:編碼資料在buffer裡的起始位置
    • length:編碼資料的長度
    • pcm:解碼後的pcm資料
    • outLen 長度至少為1的int數組,如果解碼成功,那outLen[0]被置為pcm的資料長度
  • close 關閉解碼器,參數為解碼器的句柄。關閉後句柄無效,應該置為0.
package org.easydarwin.audio;

/**
 * Created by John on 2016/3/18.
 */
public class AudioCodec {
    static {
        System.loadLibrary("AudioCodecer");
    }

    public static native int create(int codec, int sample_rate, int channels, int sample_bit);

    public static native int decode(int handle, byte[] buffer, int offset, int length, byte[] pcm, int[] outLen);

    public static native void close(int handle);
}
           

接下來我們要實作相應的native接口,頭檔案可通過調用javah指令(這裡不再詳述)來生成。以AAC解碼來說明,create代碼片段如下:

// 建立擷取解碼器
    AVCodec *pCodec = avcodec_find_decoder(AV_CODEC_ID_AAC);
    if (pCodec == NULL)
    {
        LOGI("find aac decoder error");
        printf("find aac decoder error\r\n");
        return ;
    }
    // 建立解碼Context并open解碼器
    pCodecCtx = avcodec_alloc_context3(pCodec);
    pCodecCtx->channels = channels;
    pCodecCtx->sample_rate = sample_rate;
    pCodecCtx->bit_rate = bit_rate;
    if(avcodec_open2(pCodecCtx, pCodec, NULL) < )
    {
        printf("open codec error\r\n");
        return ;
    }

    // 配置設定記憶體,存放解碼後的資料
    pFrame = av_frame_alloc();    
           

decode:

// 源碼流指派
    packet.size = length;
    packet.data = buffer;
    int got_frame = ;
    // 解碼、資料拷貝
    while (pkt_pos < nLen)
    {
    //  pkt_pos = 0;
        int got_frame = ;
        src_len = avcodec_decode_audio4(pAACD->pCodecCtx, pAACD->pFrame, &got_frame, &packet);
        if (src_len < )
        {
            return -;
        }
        data_len += src_len
        if (got_frame)
        {
            memcpy(pAACD->pFrame, pAACD->audio_buf, len);
            dst_len += len;
        }

        pkt_pos += src_len;
        packet.data = pData + pkt_pos;
        packet.size = nLen - pkt_pos;
    }

    if (NULL != outLen) 
        *outLen = dst_len;

    // 釋放
    av_free_packet(&packet);
           

close:

av_frame_free(&pFrame);
    avcodec_close(pComponent->pCodecCtx);
    avcodec_free_context(&pCodecCtx);
           

對于其它格式的音頻算法,隻要在avcodec_find_decoder時傳入不同的算法ID即可。

接下來,我們需要使用NDK編譯出ANDROID上可以使用的動态庫,為此我們需要編輯Android.mk檔案,其内容如下:

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH:= $(call my-dir)
SRC_ROOT_PATH := $(call my-dir)
LOCAL_INCLUDE := $(LOCAL_PATH)/aacdec/include
# 加載預編譯的靜态庫
include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := $(LOCAL_PATH)/aacdec/lib/libavcodec.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavdevice
LOCAL_SRC_FILES := $(LOCAL_PATH)/aacdec/lib/libavdevice.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavfilter
LOCAL_SRC_FILES := $(LOCAL_PATH)/aacdec/lib/libavfilter.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := $(LOCAL_PATH)/aacdec/lib/libavformat.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := $(LOCAL_PATH)/aacdec/lib/libavutil.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := $(LOCAL_PATH)/aacdec/lib/libswresample.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := $(LOCAL_PATH)/aacdec/lib/libswscale.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_C_INCLUDES += $(LOCAL_PATH)
LOCAL_C_INCLUDES += $(LOCAL_INCLUDE)

PROJECT_FILES := $(wildcard $(SRC_ROOT_PATH)/*.cpp)
PROJECT_FILES += $(wildcard $(SRC_ROOT_PATH)/*.c)
PROJECT_FILES += $(wildcard $(SRC_ROOT_PATH)/aacdec/*.cpp)

$(warning $(PROJECT_FILES))
PROJECT_FILES := $(PROJECT_FILES:$(LOCAL_PATH)/%=%)
$(warning $(PROJECT_FILES))
LOCAL_SRC_FILES := $(PROJECT_FILES)

LOCAL_CFLAGS := -D__unix__ -DANDROID_OS -D__arm__ -D__STDC_CONSTANT_MACROS

LOCAL_MODULE    := AudioCodecer

LOCAL_LDLIBS += -L$(LOCAL_PATH)/aacdec/lib -lavcodec -lswscale -lswresample -lavutil -lavformat -lavfilter -lavdevice -llog -lz

CFLAGS += -mfpu=neon

LOCAL_STATIC_LIBRARIES := libavcodec libswscale libswresample libavutil libavformat libavfilter libavdevice

include $(BUILD_SHARED_LIBRARY)
           

我們這裡需要把之前編譯出來的靜态庫連結起來,生成一個動态庫。其中,靜态庫lib檔案和頭檔案分别放在/aacdec/lib/ 和 /aacdec/include/ 下。

接下來,cd到jni目錄,執行ndk-build,如果順利的話,我們要的動态庫檔案就會生成。

整個項目源碼見Github