FFMEPG 平台移植,接口简化和外部模块接入 (六)ffmpeg android移植(ffmpeg android 下解码器)

前面谈了android下NDK编译,编码器,水印,等。我们再看下解码器的接口。直接上代码:

/*
 * Car eye 车辆管理平台: www.car-eye.cn
 * Car eye 开源网址: https://github.com/Car-eye-team
 * CarEyeDecoderAPI.h
 *
 * Author: Wgj
 * Date: 2018-05-16 22:54
 * Copyright 2018
 *
 * Car eye基于FFMPEG的音视频解码接口声明
 */
#ifndef __CarEyeDecoderAPI_H_
#define __CarEyeDecoderAPI_H_

#include "CarEyeTypes.h"

// 媒体帧信息定义
typedef struct
{
	// 视频编码格式
	CarEye_CodecType VCodec;
	// 音频解码格式,无音频则置为CAREYE_CODEC_NONE
	CarEye_CodecType ACodec;
	// 视频帧率(FPS)
	unsigned char	FramesPerSecond;
	// 视频宽度像素
	unsigned short	Width;
	// 视频的高度像素
	unsigned short  Height;
	// 视频码率,越高视频越清楚,相应体积也越大 如:4000000
	unsigned int	VideoBitrate;

	// 音频采样率
	unsigned int	SampleRate;
	// 音频声道数
	unsigned int	Channels;
	// 音频采样精度 16位 8位等,库内部固定为16位
	unsigned int	BitsPerSample;
	// 音频比特率 如:64000,越高声音越清楚,相应体积也越大
	unsigned int	AudioBitrate;
}CarEye_FrameInfo;

#ifdef __cplusplus
extern "C"
{
#endif

	/*
	* Comments: 创建一个解码器对象
	* Param aInfo: 要解码的媒体信息
	* @Return CarEye_Decoder_Handle 成功返回解码器对象,否则返回NULL
	*/
	CE_API CarEye_Decoder_Handle CE_APICALL CarEye_DecoderCreate(CarEye_FrameInfo aInfo);

	/*
	* Comments: 释放解码器资源
	* Param aDecoder: 要释放的解码器
	* @Return None
	*/
	CE_API void CE_APICALL CarEye_DecoderRelease(CarEye_Decoder_Handle aDecoder);

	/*
	* Comments: 获取YUV输出缓冲区的字节大小
	* Param aDecoder: 解码器
	* @Return int 输出缓冲区大小 < 0失败
	*/
	CE_API int CE_APICALL CarEye_GetYUVSize(CarEye_Decoder_Handle aDecoder);

	/*
	* Comments: 将输入视频解码为YUV420格式数据输出
	* Param aDecoder: 申请到的有效解码器
	* Param aFilter: 如需添加水印,则传入已创建的水印编码器对象
	* Param aBytes: 要进行解码的视频流
	* Param aSize: 要解码视频流字节数
	* Param aYuv: [输出] 解码成功后输出的YUV420数据
	* @Return int < 0解码失败,> 0为解码后YUV420的字节个数 ==0表示参数无效
	*/
	CE_API int CE_APICALL CarEye_DecoderYUV420(CarEye_Decoder_Handle aDecoder, 
		unsigned char *aBytes, int aSize,
		unsigned char *aYuv);

	/*
	* Comments: 将输入音频解码为PCM格式数据输出
	* Param aDecoder: 申请到的有效解码器
	* Param aBytes: 要进行解码的音频流
	* Param aSize: 要解码音频流字节数
	* Param aYuv: [输出] 解码成功后输出的PCM数据
	* @Return int < 0解码失败,> 0为解码后PCM的字节个数 ==0表示参数无效
	*/
	CE_API int CE_APICALL CarEye_DecoderPCM(CarEye_Decoder_Handle aDecoder,
		unsigned char *aBytes, int aSize,
		unsigned char *aPcm);

#ifdef __cplusplus
}
#endif


#endif // __CarEyeDecoderAPI_H_
/*
 * Car eye 车辆管理平台: www.car-eye.cn
 * Car eye 开源网址: https://github.com/Car-eye-team
 * CarEyeDecoderAPI.cpp
 *
 * Author: Wgj
 * Date: 2018-05-16 22:52
 * Copyright 2018
 *
 * Car eye基于FFMPEG的音视频解码接口实现
 */

#include <stdio.h>
#include "CarEyeDecoderAPI.h"
#include "CarEyeOSDAPI.h"

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
};
#include "public.h"


/*
* Comments: 对视频帧添加水印,非对外接口函数
* Param : aFrame: [输入/输出] 要添加水印的视频帧
* @Return void
*/
int CarEye_OSD_Add(CarEye_OSD_Handle aFilter, AVFrame *aFrame);


// 解码器结构体定义
typedef struct
{
	// 视频解码器
	AVCodecContext *VDecoder;
	// 音频解码器
	AVCodecContext *ADecoder;
	// 解码后的视频帧 音视频帧对象分别定义,防止多线程分别解码音视频造成读写冲突
	AVFrame *VFrame;
	// 解码后的音频帧
	AVFrame *AFrame;
	// 视频的像素大小
	int PixelSize;
}CarEyeDecoder;

/*
* Comments: 利用解码器对媒体包进行解码并输出解码后的数据
* Param aDecoder: 有效的解码器
* Param aPacket: 要解码的媒体数据包
* Param aFrame: [输出] 解码后的数据
* @Return int 小于0失败,等于0成功
*/
static int Decode(AVCodecContext *aDecoder, AVPacket *aPacket, AVFrame *aFrame)
{
	int ret;

	ret = avcodec_send_packet(aDecoder, aPacket);
	if (ret < 0)
	{
		printf("Error sending a packet for decoding\n");
		return ret;
	}

	return avcodec_receive_frame(aDecoder, aFrame);
}

/*
* Comments: 创建一个解码器对象
* Param aInfo: 要解码的媒体信息
* @Return CarEye_Decoder_Handle 成功返回解码器对象,否则返回NULL
*/
CE_API CarEye_Decoder_Handle CE_APICALL CarEye_DecoderCreate(CarEye_FrameInfo aInfo)
{
	if (aInfo.ACodec == CAREYE_CODEC_NONE
		&& aInfo.VCodec == CAREYE_CODEC_NONE)
	{
		// 至少包含一项解码需求
		return NULL;
	}

	CarEyeDecoder *decoder = new CarEyeDecoder;
	if (decoder == NULL)
	{
		return NULL;
	}

	memset(decoder, 0x00, sizeof(CarEyeDecoder));

	// 媒体解码器
	AVCodec *pCodec;
	if (aInfo.VCodec != CAREYE_CODEC_NONE)
	{
		// 请求视频解码器
		pCodec = avcodec_find_decoder((AVCodecID)aInfo.VCodec);
		if (pCodec == NULL)
		{
			printf("Could not find video decoder.\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}
		// 申请解码器上下文
		decoder->VDecoder = avcodec_alloc_context3(pCodec);
		if (decoder->VDecoder == NULL)
		{
			printf("Could not alloc video decoder.\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}
		decoder->VDecoder->time_base.num = 1;
		// 帧率
		decoder->VDecoder->time_base.den = aInfo.FramesPerSecond;
		// 每包一个视频帧
		decoder->VDecoder->frame_number = 1;
		// 媒体类型为视频
		decoder->VDecoder->codec_type = AVMEDIA_TYPE_VIDEO;
		decoder->VDecoder->bit_rate = aInfo.VideoBitrate;
		// 视频分辨率
		decoder->VDecoder->width = aInfo.Width;
		decoder->VDecoder->height = aInfo.Height;
		decoder->VDecoder->pix_fmt = AV_PIX_FMT_YUV420P;
		decoder->PixelSize = decoder->VDecoder->width * decoder->VDecoder->height;
		if (avcodec_open2(decoder->VDecoder, pCodec, NULL) < 0)
		{
			printf("Could not open video decoder.\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}

		decoder->VFrame = av_frame_alloc();
		if (decoder->VFrame == NULL)
		{
			printf("Alloc video frame faile!\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}
	}
	if (aInfo.ACodec != CAREYE_CODEC_NONE)
	{
		// 请求音频解码器
		pCodec = avcodec_find_decoder((AVCodecID)aInfo.ACodec);
		if (pCodec == NULL)
		{
			printf("Could not find audio decoder.\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}
		// 申请解码器上下文
		decoder->ADecoder = avcodec_alloc_context3(pCodec);
		if (decoder->ADecoder == NULL)
		{
			printf("Could not alloc audio decoder.\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}

		// 参数赋值
		decoder->ADecoder->codec_type = AVMEDIA_TYPE_AUDIO;
		decoder->ADecoder->sample_rate = aInfo.SampleRate;
		decoder->ADecoder->channels = aInfo.Channels;
		decoder->ADecoder->bit_rate = aInfo.AudioBitrate;
		decoder->ADecoder->channel_layout = AV_CH_LAYOUT_STEREO;
		if (avcodec_open2(decoder->ADecoder, pCodec, NULL) < 0)
		{
			printf("Could not open audio decoder.\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}

		decoder->AFrame = av_frame_alloc();
		if (decoder->AFrame == NULL)
		{
			printf("Alloc audio frame fail!\n");
			CarEye_DecoderRelease(decoder);
			return NULL;
		}
	}

	return decoder;
}

/*
* Comments: 释放解码器资源
* Param aDecoder: 要释放的解码器
* @Return None
*/
CE_API void CE_APICALL CarEye_DecoderRelease(CarEye_Decoder_Handle aDecoder)
{
	CarEyeDecoder *decoder = (CarEyeDecoder *)aDecoder;

	if (decoder == NULL)
	{
		return;
	}
	if (decoder->VDecoder != NULL)
	{
		avcodec_close(decoder->VDecoder);
		decoder->VDecoder = NULL;
	}
	if (decoder->ADecoder != NULL)
	{
		avcodec_close(decoder->ADecoder);
		decoder->ADecoder = NULL;
	}
	if (decoder->VFrame != NULL)
	{
		av_frame_free(&decoder->VFrame);
		decoder->VFrame = NULL;
	}
	if (decoder->AFrame != NULL)
	{
		av_frame_free(&decoder->AFrame);
		decoder->AFrame = NULL;
	}

	delete decoder;
	decoder = NULL;
}

/*
* Comments: 获取YUV输出缓冲区的字节大小
* Param aDecoder: 解码器
* @Return int 输出缓冲区大小 < 0失败
*/
CE_API int CE_APICALL CarEye_GetYUVSize(CarEye_Decoder_Handle aDecoder)
{
	CarEyeDecoder *decoder = (CarEyeDecoder *)aDecoder;
	if (decoder == NULL || decoder->VDecoder == NULL)
	{
		return -1;
	}

	int y_size = decoder->PixelSize;

	return y_size + y_size / 2;
}

/*
* Comments: 将输入视频解码为YUV420格式数据输出
* Param aDecoder: 申请到的有效解码器
* Param aFilter: 如需添加水印,则传入已创建的水印编码器对象
* Param aBytes: 要进行解码的视频流
* Param aSize: 要解码视频流字节数
* Param aYuv: [输出] 解码成功后输出的YUV420数据
* @Return int < 0解码失败,> 0为解码后YUV420的字节个数 ==0表示参数无效
*/
CE_API int CE_APICALL CarEye_DecoderYUV420(CarEye_Decoder_Handle aDecoder, 
	unsigned char *aBytes, int aSize,
	unsigned char *aYuv)
{
	CarEyeDecoder *decoder = (CarEyeDecoder *)aDecoder;
	if (decoder == NULL || decoder->VDecoder == NULL)
	{
		return 0;
	}
	if (aBytes == NULL || aSize < 1 || aYuv == NULL)
	{
		return 0;
	}

	int ret;
	int y_size;
	int out_size = 0;
	AVPacket packet = { 0 };

	packet.data = aBytes;
	packet.size = aSize;
	ret = Decode(decoder->VDecoder, &packet, decoder->VFrame);
	if (ret < 0)
	{
		printf("Decode video error.\n");
		av_packet_unref(&packet);
		return ret;
	}


	y_size = decoder->VDecoder->width * decoder->VDecoder->height;

	// 赋值Y值
	memcpy(aYuv, decoder->VFrame->data[0], y_size);
	out_size += y_size;
	memcpy(aYuv + out_size, decoder->VFrame->data[1], y_size / 4);
	out_size += (y_size / 4);
	memcpy(aYuv + out_size, decoder->VFrame->data[2], y_size / 4);
	out_size += (y_size / 4);

	av_packet_unref(&packet);

	return out_size;
}

/*
* Comments: 将输入音频解码为PCM格式数据输出
* Param aDecoder: 申请到的有效解码器
* Param aBytes: 要进行解码的音频流
* Param aSize: 要解码音频流字节数
* Param aYuv: [输出] 解码成功后输出的PCM数据
* @Return int < 0解码失败,> 0为解码后PCM的字节个数 ==0表示参数无效
*/
CE_API int CE_APICALL CarEye_DecoderPCM(CarEye_Decoder_Handle aDecoder,
	unsigned char *aBytes, int aSize,
	unsigned char *aPcm)
{
	CarEyeDecoder *decoder = (CarEyeDecoder *)aDecoder;
	if (decoder == NULL || decoder->ADecoder == NULL)
	{
		return 0;
	}
	if (aBytes == NULL || aSize < 1 || aPcm == NULL)
	{
		return 0;
	}

	int ret;
	int out_size = 0;
	AVPacket packet = { 0 };

	packet.data = aBytes;
	packet.size = aSize;

	ret = Decode(decoder->ADecoder, &packet, decoder->AFrame);
	if (ret < 0)
	{
		printf("Decode audio error.\n");
		av_packet_unref(&packet);
		return ret;
	}

	int data_size = av_get_bytes_per_sample(decoder->ADecoder->sample_fmt);

	for (int i = 0; i < decoder->AFrame->nb_samples; i++)
	{
		for (int ch = 0; ch < decoder->ADecoder->channels; ch++)
		{
			memcpy(aPcm, decoder->AFrame->data[ch] + data_size * i, data_size);
			aPcm += data_size;
			out_size += data_size;
		}
	}

	av_packet_unref(&packet);

	return out_size;
}

JNIEXPORT jlong JNICALL Java_com_CarEye_CarEyelib_ffmpegandroid_FFmpegNative_CreateDecode(JNIEnv* env, jobject obj, jobject para) {
	
	 void*  ret;
   CarEye_FrameInfo param;
   jclass jcInfo = (*env)->GetObjectClass(env, para);
   if (0 == jcInfo) {
        CarEyeLog("GetObjectClass returned 0\n");
        return 0;
   }
  int VCodec = (*env)->GetIntField(env, para, (*env)->GetFieldID(env, jcInfo, "VCodec", "I"));
  int ACodec = (*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "ACodec", "I"));
  int FramesPerSecond =(*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "FramesPerSecond", "I"));
  int Width = (*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "width", "I"));
  int Height =  (*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "height", "I"));
  int VideoBitrate = (*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "VideoBitrate", "I"));
  int SampleRate = (*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "SampleRate", "I"));
  int Channels = (*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "Channels", "I"));
  int BitsPerSample = (*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "BitsPerSample", "I"));
  int AudioBitrate=(*env)->GetIntField(env, para,(*env)->GetFieldID(env, jcInfo, "AudioBitrate", "I"));  
  param.VCodec = VCodec;
  param.ACodec = ACodec;
  param.Width = Width;
  param.Height = Height;
  param.VideoBitrate = VideoBitrate;
  param.SampleRate = SampleRate;
  param.Channels = Channels;
  param.BitsPerSample = BitsPerSample;
  param.AudioBitrate = AudioBitrate;
  ret = CarEye_DecoderCreate( param);
  if(  ret  == NULL) {
      return 0;
  }else
  {
      return (long)ret;
  } 
	
}
JNIEXPORT jint JNICALL Java_com_CarEye_CarEyelib_ffmpegandroid_FFmpegNative_Decode(JNIEnv* env, jobject obj, jlong handle,jint flag, jbyteArray frame, jbyteArray OutFrame) {
    void* pHandle;
    int ret;
    unsigned char* in_data;
    unsigned char* out_data;
    CarEye_YUVFrame yuv_frame;
    if(handle==0)
        return -1;
    pHandle = (void*)handle;
    in_data = (*env)->GetByteArrayElements(env,frame, 0 );
    int len = (*env)->GetArrayLength(env,frame);
    out_data = (*env)->GetByteArrayElements(env,OutFrame, 0 );    
    if( flag == 0)
		{
			ret = CarEye_DecoderYUV420(pHandle, in_data, len, out_data);
		}else
		{
			CarEye_DecoderPCM(pHandle, in_data, len, out_data);
		} 
		(*env)->ReleaseByteArrayElements(env,frame,in_data,0);
    (*env)->ReleaseByteArrayElements(env,OutFrame,out_data,0);
    
    return ret;
}

JNIEXPORT jint JNICALL Java_com_CarEye_CarEyelib_ffmpegandroid_FFmpegNative_ReleaseDecode(JNIEnv* env, jobject obj, jlong handle) {
    void* pHandle;
    if(handle==0)
        return -1;
    pHandle = (void*)handle;
    CarEye_DecoderRelease(pHandle);
    return 0;
}

几个简单接口实现了视频,音频解码。上层调用。

至此我们完成了基本的FFMPEG 主要android接口,包括编码器,解码器,水印文字。满足了绝大部分的用户的需求。


car-eye开源官方网址:www.car-eye.cn  

car-eye 流媒体平台网址:www.liveoss.com   

car-eye 技术官方邮箱: support@car-eye.cn    
car-eye技术交流QQ群: 590411159     


CopyRight©  car-eye 开源团队 2018



上一篇: FFMEPG 平台移植,接口简化和外部模块接入 (五)ffmpeg android移植(ffmpeg android studio 静态编译)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值