H264编码(android+ffmpeg)

在android下调用第三方开源库ffmpeg,把手机预览数据流(AV_PIX_FMT_NV12)编码成AV_CODEC_ID_H264格式的视频流。
写的一个简单的c++ demo如下

Android.mk里添加如下引用
LOCAL_C_INCLUDES += $(LOCAL_PATH)/ext/ffmpeg/default/3.1.2/include/android/
LOCAL_SHARED_LIBRARIES += libffmpeg

my_h264.h

#ifndef __MY_H264_H__
#define __MY_H264_H__

#ifdef __cplusplus
extern "C" {
#endif
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#ifdef __cplusplus
}
#endif
#include 
#include "my_mutex.h"

typedef enum{
    QUALITY_VERY_HIGHT,
    QUALITY_HIGHT,
    QUALITY_MEAIUM,
    QUALITY_LOW,
    QUALITY_VERYLOW
}eEncoderQuality;

class MyH264
{

public:
    MyH264();
    virtual ~MyH264();
    int reset();
    int open(AVPixelFormat nAVPixelFormat, int FrameWidth, int FrameHeight, int FrameRate, eEncoderQuality EncoderQuality);
    int close();
    int record(unsigned char* BufY);
    int setData(AVPacket* pPacket);
    int getData(uint8_t** Data, int* Size);

private:
    AVFormatContext* mpAVFormatContext;
    AVCodecContext* mpAVCodecContext;
    AVStream* mpAVStream;
    AVFrame* mpAVFrame;
    
    uint8_t* mFrameInData;
    int mFrameInSize;
    
    uint8_t* mFrameOutData;
    int mFrameOutSize;
    
    double mFrameInterval;
    unsigned int mFrameIndex;
    int mDataReadyFlag;
    Mutex mLock;

};

#endif//__MY_H264_H__

my_h264.cpp

#include "my_h264.h"
#include "my_log.h"

#define TIME_BASE_DEN (500)
#define OUT_BUF_SIZE_MAX (1000000)

MyH264::MyH264()
{
    MY_LOGE("+");
    reset();
    MY_LOGE("-");
}

MyH264::~MyH264()
{
    MY_LOGE("+");
    close();
    MY_LOGE("-");
}

int MyH264::reset()
{
    MY_LOGE("+");

    mpAVFormatContext = NULL;
    mpAVCodecContext = NULL;
    mpAVStream = NULL;
    mpAVFrame = NULL;
    mFrameInData = NULL;
    mFrameOutData = NULL;
    mFrameOutSize = 0;
    mFrameInterval = 0.0;
    mFrameIndex = 0;
    mDataReadyFlag = 0;
    
    MY_LOGE("-");
    return 0;
}

int MyH264::open(AVPixelFormat nAVPixelFormat, int FrameWidth, int FrameHeight, int FrameRate, eEncoderQuality EncoderQuality)
{
    Mutex::Autolock lock(mLock);
    MY_LOGE("+");
    
    reset();
    
    int ret = 0;
    mFrameInterval = (1.0 / (double)FrameRate);
    
    char strEncoderParam[5][64];
    memset(strEncoderParam, 0, sizeof(strEncoderParam));
    switch (EncoderQuality)
    {
        case QUALITY_VERYLOW:
            strcpy(strEncoderParam[0], "30");
            strcpy(strEncoderParam[1], "4.1");
            strcpy(strEncoderParam[2], "superfast");
            strcpy(strEncoderParam[3], "zerolatency");
            strcpy(strEncoderParam[4], "baseline");
            break;
        case QUALITY_LOW:
            strcpy(strEncoderParam[0], "25");
            strcpy(strEncoderParam[1], "4.1");
            strcpy(strEncoderParam[2], "superfast");
            strcpy(strEncoderParam[3], "zerolatency");
            strcpy(strEncoderParam[4], "baseline");
            break;
        case QUALITY_MEAIUM:
            strcpy(strEncoderParam[0], "22");
            strcpy(strEncoderParam[1], "4.1");
            strcpy(strEncoderParam[2], "superfast");
            strcpy(strEncoderParam[3], "zerolatency");
            strcpy(strEncoderParam[4], "baseline");
            break;
        case QUALITY_HIGHT:
            strcpy(strEncoderParam[0], "20");
            strcpy(strEncoderParam[1], "4.1");
            strcpy(strEncoderParam[2], "medium");
            strcpy(strEncoderParam[3], "zerolatency");
            strcpy(strEncoderParam[4], "main");
            break;
        case QUALITY_VERY_HIGHT:
            strcpy(strEncoderParam[0], "17");
            strcpy(strEncoderParam[1], "5.1");
            strcpy(strEncoderParam[2], "slow");
            strcpy(strEncoderParam[3], "zerolatency");
            strcpy(strEncoderParam[4], "high");
            break;
        default:
            break;
    }  

    avcodec_register_all();
    av_register_all();

    mpAVFormatContext = avformat_alloc_context();
    if (!mpAVFormatContext)
    {
        MY_LOGE("avformat_alloc_context error");
        close();
        return -1;
    }

    AVCodec* pAVCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!pAVCodec)
    {
        MY_LOGE("avcodec_find_encoder error");
        close();
        return -1;
    }

    mpAVStream = avformat_new_stream(mpAVFormatContext, pAVCodec);
    if (!mpAVStream) {
        MY_LOGE("avformat_new_stream error");
        close();
        return -1;
    }
    mpAVStream->avg_frame_rate.num = TIME_BASE_DEN;
    mpAVStream->avg_frame_rate.den = 1;

    mpAVCodecContext = mpAVStream->codec;
    mpAVCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
    mpAVCodecContext->codec_id = AV_CODEC_ID_H264;
    mpAVCodecContext->codec_type = AVMEDIA_TYPE_VIDEO;
    mpAVCodecContext->bit_rate = 400000;
    mpAVCodecContext->width = FrameWidth;
    mpAVCodecContext->height = FrameHeight;
    mpAVCodecContext->time_base.den = TIME_BASE_DEN;//mFrameRate;
    mpAVCodecContext->time_base.num = 1;
    mpAVCodecContext->gop_size = 12;
    mpAVCodecContext->pix_fmt = nAVPixelFormat;
    mpAVCodecContext->qmin = 10;
    mpAVCodecContext->qmax = 51;
    mpAVCodecContext->max_b_frames = 3; 

    av_opt_set(mpAVCodecContext->priv_data, "crf",    strEncoderParam[0], 0);
    av_opt_set(mpAVCodecContext->priv_data, "level",  strEncoderParam[1], 0);
    av_opt_set(mpAVCodecContext->priv_data, "preset", strEncoderParam[2], 0);
    av_opt_set(mpAVCodecContext->priv_data, "tune",   strEncoderParam[3], 0);

    AVDictionary *pAVDictionary = NULL;
    av_dict_set(&pAVDictionary, "profile", strEncoderParam[4], 0);

    ret = avcodec_open2(mpAVCodecContext, pAVCodec, &pAVDictionary);
    if (ret < 0)
    {
        MY_LOGE("avcodec_open2 error");
        av_dict_free(&pAVDictionary);
        close();
        return -1;
    }
    
    if (ret < 0)
    {
        MY_LOGE("avformat_write_header error");
        close();
        return -1;
    }
    
    mpAVFrame = av_frame_alloc();
    if(!mpAVFrame)
    {
        MY_LOGE("av_frame_alloc error");
        close();
        return -1;
    }
    mpAVFrame->pts = 0;

    mFrameInSize = avpicture_get_size(mpAVCodecContext->pix_fmt, mpAVCodecContext->width, mpAVCodecContext->height);
    MY_LOGE("mFrameInSize=%d", mFrameInSize);
    mFrameInData = new uint8_t[mFrameInSize];
    if(!mFrameInData)
    {
        MY_LOGE("new mFrameInData error");
        close();
        return -1;
    }
    mFrameOutData = new uint8_t[OUT_BUF_SIZE_MAX];
    if(!mFrameOutData)
    {
        MY_LOGE("new mFrameOutData error");
        close();
        return -1;
    }

    MY_LOGE("-");    
    return 0;
}


int MyH264::close()
{
    Mutex::Autolock lock(mLock);
    MY_LOGE("+");

    if(mpAVCodecContext)
    {
        avcodec_close(mpAVCodecContext);
        mpAVCodecContext = NULL;
    }
    
    if (mpAVFormatContext)
    {
        avio_close(mpAVFormatContext->pb);
        avformat_free_context(mpAVFormatContext);
        mpAVFormatContext = NULL;        
    }
    
    if (mpAVFrame)
    {
        av_frame_free(&mpAVFrame);
        mpAVFrame = NULL;
    }
    
    if (mFrameInData)
    {
        delete [] mFrameInData;
        mFrameInData = NULL;
    }
    
    if (mFrameOutData)
    {
        delete [] mFrameOutData;
        mFrameOutData = NULL;
    }

    reset();
    
    MY_LOGE("-");    
    return 0;
}

int MyH264::record(unsigned char* BufY)
{
    Mutex::Autolock lock(mLock);

    double TimeStemp =  mFrameInterval * (double)mFrameIndex;
    unsigned int nPtsIndex = (int)(TimeStemp * TIME_BASE_DEN + 0.5);
    MY_LOGE("+ mFrameIndex=%d TimeStemp=%lf , nPtsIndex=%d", mFrameIndex, TimeStemp, nPtsIndex);

    memset(mFrameInData, 0, mFrameInSize);
    memcpy(mFrameInData, BufY, mFrameInSize);

    int ret = 0;
    ret = av_image_fill_arrays(mpAVFrame->data, mpAVFrame->linesize, mFrameInData, mpAVCodecContext->pix_fmt, mpAVCodecContext->width, mpAVCodecContext->height, 1);

    if (ret < 0)
    {
        MY_LOGE("av_image_fill_arrays error");
        close();
        return -1;
    }
    
    AVPacket Packet = { 0 };
    av_init_packet(&Packet);
    
    mpAVFrame->pict_type = AV_PICTURE_TYPE_NONE;
    mpAVFrame->pts = nPtsIndex * av_rescale_q(1, mpAVCodecContext->time_base, mpAVStream->time_base);

    int GotPacket = 0;
    ret = avcodec_encode_video2(mpAVCodecContext, &Packet, mpAVFrame, &GotPacket);
    if (ret < 0)
    {
        MY_LOGE("avcodec_encode_video2 error");
        av_packet_unref(&Packet);
        close();
        return -1;
    }
    //MY_LOGE("GotPacket=%d Packet.size=%d", GotPacket, Packet.size);

    if (!ret && GotPacket && Packet.size)
    {
        Packet.stream_index = mpAVStream->index;
        setData(&Packet);
    }

    av_packet_unref(&Packet);
    
    mFrameIndex++;
    
    MY_LOGE("-");
    return 0;
}

int MyH264::setData(AVPacket* pPacket)
{
    MY_LOGE("+ mFrameIndex=%d pPacket->flags=%d", mFrameIndex, pPacket->flags);
    memset(mFrameOutData, 0, OUT_BUF_SIZE_MAX);
    if (pPacket->flags == AV_PKT_FLAG_KEY)
    {
        memcpy(mFrameOutData, mpAVCodecContext->extradata, mpAVCodecContext->extradata_size);
        memcpy((mFrameOutData + mpAVCodecContext->extradata_size), pPacket->data, pPacket->size);
        mFrameOutSize = mpAVCodecContext->extradata_size + pPacket->size;
    }
    else
    {
        memcpy(mFrameOutData, pPacket->data, pPacket->size);
        mFrameOutSize = pPacket->size;
    }
    mDataReadyFlag = 1;

    return 0;
    MY_LOGE("-");
}

int MyH264::getData(uint8_t** Data, int* Size)
{
    Mutex::Autolock lock(mLock);

    MY_LOGE("+ mDataReadyFlag=%d mFrameOutSize=%d", mDataReadyFlag, mFrameOutSize);
    
    if (mDataReadyFlag == 0)
    {
        MY_LOGE("data no ready");
        return -1;
    }

    *Size = mFrameOutSize;
    *Data = mFrameOutData;
    mDataReadyFlag = 0;

    MY_LOGE("-");
    return 0;
}

你可能感兴趣的:(H264编码(android+ffmpeg))