zoukankan      html  css  js  c++  java
  • ffmpeg学习笔记-多线程音视频解码

    之前的视频解码仍然存在问题,那就是是在主线程中去完成解码的,会造成线程阻塞,这里将其改为多线程解码,使其主线程不被阻塞

    前面介绍了音视频的主线程解码,那样会阻塞主线程,在前面学习了多线程以后,就可以对音频和视频分离开来在子线程里解析了,但这样存在音视频同步的问题了,这里贴出代码,只是提供一种思路,其运行存在大量问题,还需要慢慢解决。例如,退出发生异常,音视频不同步

    #include <android/log.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <android/native_window.h>
    #include <android/native_window_jni.h>
    
    #include "com_cj5785_ffmpegvideothread_VideoPlayer.h"
    
    #include "include/ffmpeg/libavformat/avformat.h"
    #include "include/ffmpeg/libavcodec/avcodec.h"
    #include "include/ffmpeg/libswscale/swscale.h"
    #include "include/ffmpeg/libswresample/swresample.h"
    #include "include/libyuv/libyuv.h"
    
    #define LOGI(FORMAT,...) __android_log_print(4,"cj5785",FORMAT,##__VA_ARGS__);
    #define LOGE(FORMAT,...) __android_log_print(6,"cj5785",FORMAT,##__VA_ARGS__);
    
    #define MAX_STREAM 2
    #define VIDEO_STREAM_TYPE 0
    #define AUDIO_STREAM_TYPE 1
    #define MAX_AUDIO_FRME_SIZE 48000 * 4
    
    typedef struct _Player
    {
    	JavaVM *javaVM;
    	AVFormatContext *input_format_ctx;
    	int video_stream_index;
    	int audio_stream_index;
    	AVCodecContext *input_codec_ctx[MAX_STREAM];
    	pthread_t decode_threads[MAX_STREAM];
    	ANativeWindow* nativeWindow;
    	SwrContext *swr_ctx;
    	enum AVSampleFormat in_sample_fmt;
    	enum AVSampleFormat out_sample_fmt;
    	int in_sample_rate;
    	int out_sample_rate;
    	int out_channel_nb;
    	jobject audio_track;
    	jmethodID audio_track_write_mid;
    }Player;
    
    void init_input_format_ctx(Player *player, const char *input)
    {
    	//注册组件
    	av_register_all();
    
    	AVFormatContext *format_ctx = avformat_alloc_context();
    	//打开视频文件
    	if(avformat_open_input(&format_ctx, input, NULL, NULL) != 0)
    	{
    		LOGE("%s", "打开文件失败!");
    		return;
    	}
    
    	//获取视频相关信息
    	if(avformat_find_stream_info(format_ctx, NULL) < 0)
    	{
    		LOGE("%s", "获取视频信息失败!");
    		return;
    	}
    
    	//判断输入流的类型
    	int i = 0;
    	for (i = 0; i < format_ctx->nb_streams; i++)
    	{
    		if(format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
    		{
    			player->video_stream_index = i;
    		}
    		else if(format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
    		{
    			player->audio_stream_index = i;
    		}
    	}
    
    	player->input_format_ctx = format_ctx;
    
    }
    
    void init_codec_context(Player *player,int stream_idx){
    	AVFormatContext *format_ctx = player->input_format_ctx;
    	//获取解码器
    	AVCodecContext *codec_ctx = format_ctx->streams[stream_idx]->codec;
    	AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
    	if(codec == NULL){
    		LOGE("%s","无法解码");
    		return;
    	}
    	//打开解码器
    	if(avcodec_open2(codec_ctx, codec, NULL) < 0){
    		LOGE("%s","解码器无法打开");
    		return;
    	}
    	player->input_codec_ctx[stream_idx] = codec_ctx;
    }
    
    void decode_video_prepare(JNIEnv *env,Player *player,jobject surface){
    	player->nativeWindow = ANativeWindow_fromSurface(env, surface);
    }
    
    void decode_video(Player *player,AVPacket *packet)
    {
    	//像素数据(解码数据)
    	AVFrame *yuv_frame = av_frame_alloc();
    	AVFrame *rgb_frame = av_frame_alloc();
    	//绘制时的缓冲区
    	ANativeWindow_Buffer outBuffer;
    	AVCodecContext *codec_ctx = player->input_codec_ctx[player->video_stream_index];
    	int got_frame;
    	//解码AVPacket->AVFrame
    	avcodec_decode_video2(codec_ctx, yuv_frame, &got_frame, packet);
    	//Zero if no frame could be decompressed
    	//非零,正在解码
    	if(got_frame){
    		//lock
    		//设置缓冲区的属性(宽、高、像素格式)
    		ANativeWindow_setBuffersGeometry(player->nativeWindow, codec_ctx->width, codec_ctx->height,WINDOW_FORMAT_RGBA_8888);
    		ANativeWindow_lock(player->nativeWindow,&outBuffer,NULL);
    
    		//设置rgb_frame的属性(像素格式、宽高)和缓冲区
    		//rgb_frame缓冲区与outBuffer.bits是同一块内存
    		avpicture_fill((AVPicture *)rgb_frame, outBuffer.bits, AV_PIX_FMT_RGBA, codec_ctx->width, codec_ctx->height);
    
    		//YUV->RGBA_8888
    		I420ToARGB(yuv_frame->data[0],yuv_frame->linesize[0],
    				yuv_frame->data[2],yuv_frame->linesize[2],
    				yuv_frame->data[1],yuv_frame->linesize[1],
    				rgb_frame->data[0], rgb_frame->linesize[0],
    				codec_ctx->width,codec_ctx->height);
    
    		//unlock
    		ANativeWindow_unlockAndPost(player->nativeWindow);
    
    		usleep(1000 * 16);
    	}
    
    	av_frame_free(&yuv_frame);
    	av_frame_free(&rgb_frame);
    }
    
    void decode_audio_prepare(Player *player){
    	SwrContext *swr_ctx = swr_alloc();
    	AVCodecContext *codec_ctx = player->input_codec_ctx[player->audio_stream_index];
    	enum AVSampleFormat in_sample_fmt = codec_ctx->sample_fmt;
    	enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    	int in_sample_rate = codec_ctx->sample_rate;
    	int out_sample_rate = in_sample_rate;
    	uint64_t in_ch_layout = codec_ctx->channel_layout;
    	uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    	swr_alloc_set_opts(swr_ctx,
    			out_ch_layout, out_sample_fmt, out_sample_rate,
    			in_ch_layout, in_sample_fmt, in_sample_rate,
    			0, NULL);
    	swr_init(swr_ctx);
    	int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
    
    	player->in_sample_fmt = in_sample_fmt;
    	player->out_sample_fmt = out_sample_fmt;
    	player->in_sample_rate = in_sample_rate;
    	player->out_sample_rate = out_sample_rate;
    	player->out_channel_nb = out_channel_nb;
    	player->swr_ctx = swr_ctx;
    }
    
    void jni_audio_prepare(JNIEnv *env, jobject jobj, Player *player)
    {
    	jclass player_class = (*env)->GetObjectClass(env, jobj);
    	jmethodID create_audio_track_mid = (*env)->GetMethodID(env, player_class, "createAudioTrack", "(II)Landroid/media/AudioTrack;");
    	jobject audio_track = (*env)->CallObjectMethod(env, jobj, create_audio_track_mid, player->out_sample_rate, player->out_channel_nb);
    	jclass audio_track_class = (*env)->GetObjectClass(env, audio_track);
    	jmethodID audio_track_play_mid = (*env)->GetMethodID(env, audio_track_class, "play", "()V");
    	(*env)->CallVoidMethod(env, audio_track, audio_track_play_mid);
    	jmethodID audio_track_write_mid = (*env)->GetMethodID(env, audio_track_class, "write", "([BII)I");
    
    	player->audio_track = (*env)->NewGlobalRef(env, audio_track);
    	player->audio_track_write_mid = audio_track_write_mid;
    }
    
    void decode_audio(Player *player, AVPacket *packet)
    {
    	JNIEnv *env = NULL;
    	JavaVM *javaVM = player->javaVM;
    	(*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
    	uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRME_SIZE);
    	AVCodecContext *codec_ctx = player->input_codec_ctx[player->audio_stream_index];
    	AVFrame *frame = av_frame_alloc();
    	int got_frame = 0;
    	avcodec_decode_audio4(codec_ctx, frame, &got_frame, packet);
    	if(got_frame)
    	{
    		swr_convert(player->swr_ctx, &out_buffer, MAX_AUDIO_FRME_SIZE,
    				(const uint8_t **)frame->data, frame->nb_samples);
    		int out_buffer_size = av_samples_get_buffer_size(NULL, player->out_channel_nb,
    				frame->nb_samples ,player->out_sample_fmt, 1);
    		jbyteArray audio_sample_array = (*env)->NewByteArray(env, out_buffer_size);
    		jbyte *sample_byte = (*env)->GetByteArrayElements(env, audio_sample_array, NULL);
    		memcpy(sample_byte, out_buffer, out_buffer_size);
    		(*env)->ReleaseByteArrayElements(env, audio_sample_array, sample_byte, 0);
    		(*env)->CallIntMethod(env, player->audio_track, player->audio_track_write_mid,
    				audio_sample_array, 0, out_buffer_size);
    		(*env)->DeleteLocalRef(env,audio_sample_array);
    		(*javaVM)->DetachCurrentThread(javaVM);
    		usleep(16 * 1000);
    	}
    	av_frame_free(&frame);
    }
    
    void* decode_data(void* arg){
    	Player *player = (struct Player*)arg;
    	AVFormatContext *format_ctx = player->input_format_ctx;
    	//编码数据
    	AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    	//6.一阵一阵读取压缩的视频数据AVPacket
    	int video_frame_count = 0;
    	while(av_read_frame(format_ctx,packet) >= 0){
    		if(packet->stream_index == player->video_stream_index)
    		{
    			//decode_video(player,packet);
    		}else if(packet->stream_index == player->audio_stream_index)
    		{
    			decode_audio(player,packet);
    		}
    		av_free_packet(packet);
    	}
    }
    
    JNIEXPORT void JNICALL Java_com_cj5785_ffmpegvideothread_VideoPlayer_play
      (JNIEnv *env, jobject jobj, jstring jstr_path, jobject obj_surface)
    {
    	LOGE("%s", "开始");
    
    	const char *input_cstr = (*env)->GetStringUTFChars(env, jstr_path, NULL);
    	Player *player = (Player *)calloc(sizeof(Player), 1);
    
    	(*env)->GetJavaVM(env,&(player->javaVM));
    
    	//初始化封装格式上下文
    	init_input_format_ctx(player,input_cstr);
    	int video_stream_index = player->video_stream_index;
    	int audio_stream_index = player->audio_stream_index;
    	//获取音视频解码器,并打开
    	init_codec_context(player,video_stream_index);
    	init_codec_context(player,audio_stream_index);
    
    	decode_video_prepare(env, player, obj_surface);
    	decode_audio_prepare(player);
    	jni_audio_prepare(env,jobj,player);
    
    	//创建子线程解码
    	pthread_create(&(player->decode_threads[video_stream_index]),NULL,decode_data,(void*)player);
    	pthread_create(&(player->decode_threads[audio_stream_index]),NULL,decode_data,(void*)player);
    
    
    	/*ANativeWindow_release(nativeWindow);
    	av_frame_free(&yuv_frame);
    	avcodec_close(pCodeCtx);
    	avformat_free_context(pFormatCtx);
    
    	(*env)->ReleaseStringUTFChars(env,input_jstr,input_cstr);
    
    	free(player);*/
    
    }
    
  • 相关阅读:
    在Flask/Django中增加下载Excel的功能
    如何设置原创声明
    python3中用django下载文件,中文名乱码怎么办?
    关于状态机工作流的编程方法
    用python解析word文件(三):style
    用python解析word文件(二):table
    用python解析word文件(一):paragraph
    python中的轻量级定时任务调度库:schedule
    在windows下的虚拟环境中使用tk,要留神了
    压平列表
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664646.html
Copyright © 2011-2022 走看看