使用FFmpeg解码H264-2016.01.14

使用jni方式调用FFmepg项目中接口,对H264裸码进行解码。

该Demo主要实现从文件中读取H264编码的视频流,然后使用FFmpeg解码,将解码后的码流保存到文件。

工程目录结构如图所示:

                                     image

Android.mk文件内容如下

LOCAL_PATH := $(call my-dir)  
  
# FFmpeg library  
include $(CLEAR_VARS)  
LOCAL_MODULE := avcodec  
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libavcodec-56.so  
include $(PREBUILT_SHARED_LIBRARY)  
  
include $(CLEAR_VARS)  
LOCAL_MODULE := avutil  
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libavutil-54.so  
include $(PREBUILT_SHARED_LIBRARY)  

include $(CLEAR_VARS)  
LOCAL_MODULE := swresample  
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libswresample-1.so  
include $(PREBUILT_SHARED_LIBRARY)  
  
include $(CLEAR_VARS)  
LOCAL_MODULE := swscale  
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libswscale-3.so  
include $(PREBUILT_SHARED_LIBRARY)  

# Program  
include $(CLEAR_VARS)  
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := avcodec swscale avutil swresample
include $(BUILD_SHARED_LIBRARY)

Application.mk内容如下:

APP_ABI := armeabi

HelloJni.java内容如下:

package com.example.hellojni;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;


public class HelloJni extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        TextView  tv = new TextView(this);
        if(DecodeH264Video())
        {
            tv.setText("Decode Video Success");
        }
        else
        {
            tv.setText("Decode Video Failed");
        }
        setContentView(tv);
    }

    public native boolean  DecodeH264Video();

    static {
        System.loadLibrary("avcodec-56");
        System.loadLibrary("swscale-3");
        System.loadLibrary("hello-jni");  
        System.loadLibrary("avutil-54"); 
        System.loadLibrary("swresample-1");  
    }
}

hello-jni.c文件内容如下:

#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include <stdio.h>
#include <string.h>
#include <jni.h>
#include <android/log.h>

typedef enum
{
    FALSE = 0, TRUE = 1,
} C_BOOL;

typedef unsigned char uint8_t;
const int IN_BUFFER_SIZE = 4096;

#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__)
#define LOGD(format, ...)  __android_log_print(ANDROID_LOG_DEBUG, "(-_-)", format, ##__VA_ARGS__)

static C_BOOL __DecodeH264Video(FILE* fp_in, FILE* fp_out);

JNIEXPORT jboolean JNICALL Java_com_example_hellojni_HelloJni_DecodeH264Video(JNIEnv *env, jobject obj)
{
    char filepath_in[] = "/data/video/bxjg_352x288.h264";
    FILE *fp_in = fopen(filepath_in, "rb");
    if (NULL == fp_in)
    {
        LOGE("open input h264 video file failed, filename [%s]", filepath_in);
        return (jboolean) FALSE;
    }

    char filepath_out[] = "/data/video/bxjg_352x288.yuv";
    FILE *fp_out = fopen(filepath_out, "wb");
    if (NULL == fp_out)
    {
        LOGE("open output yuv video file failed, filename [%s]", filepath_out);
        return (jboolean) FALSE;
    }

    LOGD("open input and output file success");

    if (TRUE == __DecodeH264Video(fp_in, fp_out))
    {
        LOGD("decode h264 video success");
    }
    else
    {
        LOGE("decode h264 video failed");
        return (jboolean) FALSE;
    }

    fclose(fp_in);
    fclose(fp_out);

    return (jboolean) TRUE;
}

C_BOOL __DecodeH264Video(FILE* fp_in, FILE* fp_out)
{
    avcodec_register_all();

    AVCodec *pCodec = NULL;
    pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (NULL == pCodec)
    {
        LOGE("avcodec_find_decoder failed");
        return FALSE;
    }

    AVCodecContext *pCodecCtx = NULL;
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (NULL == pCodecCtx)
    {
        LOGE("avcodec_alloc_context3 failed");
        return FALSE;
    }

    AVCodecParserContext *pCodecParserCtx = NULL;
    pCodecParserCtx = av_parser_init(AV_CODEC_ID_H264);
    if (NULL == pCodecParserCtx)
    {
        LOGE("av_parser_init failed");
        return FALSE;
    }

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        LOGE("avcodec_open2 failed");
        return FALSE;
    }

    AVFrame *pFrame = NULL;
    pFrame = av_frame_alloc();
    if (NULL == pFrame)
    {
        LOGE("av_frame_alloc failed");
        return FALSE;
    }

    AVPacket packet;
    av_init_packet(&packet);

    uint8_t in_buffer[IN_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
    memset(in_buffer, 0, sizeof(in_buffer));
    uint8_t *cur_ptr = NULL;
    int cur_size = 0;
    int ret = 0;
    int got_picture = 0;
    int y_size = 0;
    int first_time = 1;

    struct SwsContext *img_convert_ctx = NULL;
    AVFrame *pFrameYUV = NULL;
    uint8_t *out_buffer = NULL;

    while (TRUE)
    {
        cur_size = fread(in_buffer, 1, IN_BUFFER_SIZE, fp_in);
        if (0 == cur_size)
        {
            break;
        }

        cur_ptr = in_buffer;
        while (cur_size > 0)
        {
            int parse_len = av_parser_parse2(pCodecParserCtx, pCodecCtx, &packet.data, &packet.size, cur_ptr, cur_size,
                    AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);

            cur_ptr += parse_len;
            cur_size -= parse_len;

            if (0 == packet.size)
            {
                continue;
            }

            LOGD("packet size [%d]", packet.size);

            switch (pCodecParserCtx->pict_type)
            {
                case AV_PICTURE_TYPE_I:
                {
                    LOGD("AV_PICTURE_TYPE_I");
                    break;
                }
                case AV_PICTURE_TYPE_P:
                {
                    LOGD("AV_PICTURE_TYPE_P");
                    break;
                }
                case AV_PICTURE_TYPE_B:
                {
                    LOGD("AV_PICTURE_TYPE_B");
                    break;
                }
                default:
                {
                    LOGD("OTHER_PICTURE_TYPE");
                    break;
                }
            }

            LOGD("CodecParserCtx->output_picture_number [%d]", pCodecParserCtx->output_picture_number);

            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);

            if (ret < 0)
            {
                LOGE("avcodec_decode_video2 failed");
                return FALSE;
            }

            if (got_picture)
            {
                if (first_time)
                {
                    LOGD("CodecCtx->codec->long_name [%s]", pCodecCtx->codec->long_name);
                    LOGD("CodecCtx->width [%d], CodecCtx->height [%d]", pCodecCtx->width, pCodecCtx->height);

                    //SwsContext
                    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
                            pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

                    pFrameYUV = av_frame_alloc();

                    out_buffer = (uint8_t *) av_malloc(
                            avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));

                    avpicture_fill((AVPicture *) pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width,
                            pCodecCtx->height);

                    y_size = pCodecCtx->width * pCodecCtx->height;

                    first_time = 0;
                }

                sws_scale(img_convert_ctx, (const uint8_t* const *) pFrame->data, pFrame->linesize, 0,
                        pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

                fwrite(pFrameYUV->data[0], 1, y_size, fp_out); //Y
                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_out); //U
                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_out); //V

                LOGD("succeed to decode one frame");

            }
        }

    }

    //Flush Decoder
    packet.data = NULL;
    packet.size = 0;

    while (TRUE)
    {
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
        if (ret < 0)
        {
            LOGE("avcodec_decode_video2 failed");
            return FALSE;
        }

        if (!got_picture)
        {
            break;
        }

        if (got_picture)
        {

            sws_scale(img_convert_ctx, (const uint8_t* const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
                    pFrameYUV->data, pFrameYUV->linesize);

            fwrite(pFrameYUV->data[0], 1, y_size, fp_out); //Y
            fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_out); //U
            fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_out); //V

            LOGD("Flush Decoder: Succeed to decode 1 frame");
        }
    }

    sws_freeContext(img_convert_ctx);
    av_frame_free(&pFrameYUV);
    av_parser_close(pCodecParserCtx);
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    av_free(pCodecCtx);

    return TRUE;
}

如果仅使用FFmpeg中的解码功能,可用如下的配置选项对编译的库进行瘦身,使得编译出来的解码动态库libavcodec.so大小为2M左右!

#!/bin/bash
NDK="/home/alchen/android-ndk-r9d"
TARGET="android-19"
SYSROOT="$NDK/platforms/$TARGET/arch-arm"
TOOLCHAIN="$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64"

function build_one
{
./configure 
    --prefix=$PREFIX 
    --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- 
    --target-os=linux 
    --enable-decoder=h264 
    --enable-shared 
    --enable-version3 
    --enable-gpl 
    --enable-nonfree 
    --enable-protocol=file 
    --enable-avfilter 
    --enable-cross-compile 
    --enable-asm 
    --enable-neon 
    --enable-armv5te 
    --disable-static 
    --disable-decoders 
    --disable-doc 
    --disable-muxers 
    --disable-demuxers 
    --disable-bsfs 
    --disable-indevs 
    --disable-outdevs 
    --disable-filters 
    --disable-ffmpeg 
    --disable-ffplay 
    --disable-ffserver 
    --disable-ffprobe 
    --disable-encoders 
    --disable-devices 
    --disable-protocols 
    --disable-network 
    --disable-avdevice 
    --arch=arm 
    --sysroot=$SYSROOT 
    --extra-cflags="-Os -fpic $ADDI_CFLAGS" 
    --extra-ldflags="$ADDI_LDFLAGS" 
    $ADDITIONAL_CONFIGURE_FLAG
    make clean
    make
    make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one
原文地址:https://www.cnblogs.com/zhouLee/p/5129886.html