一.AVFrame
用來存放解碼后的數據。
【相關函數】
AVFrame *frame = av_frame_alloc(); // 空間分配,分配一個空間並初始化。
void av_frame_free(AVFrame **frame); // 空間釋放。兩種釋放方式,一種是將引用計數-1,
int av_frame_ref(AVFrame *dst, const AVFrame *src); // 引用計數增加1。比如要在多線程訪問的時候復制到另外一邊,就可以利用引用計數的變化。
AVFrame *av_frame_clone(const AVFrame *src); // 復制。也是重新創建一個空間,然后引用計數加+1。與AVPacket的區別在於:AVFrame的復制開銷更大。1920*1080p的視頻,一幀可能就有幾MB,一秒鍾可能就有幾百MB,所以做一幀畫面的內存復制可能都耗費到毫秒級別,不像AVPacket可能只有微秒級別,會影響幀率。所以在它的空間復制上一定要慎重,所以我們一般用引用計數的方式來做。
void av_frame_unref(AVFrame *frame); // 直接把對象的引用計數-1.
【結構體包含的內容】
uint8_t *data[AV_NUM_DATA,POINTERS] // 存放的數據。
int linesize[AV_NUM_DATA,POINTERS] // 對於視頻就是一行數據的大小。對於音頻就是一個通道數據的大小。
int width, int height, int nb_samples // 視頻部分, 音頻相關(單通道的樣本數量)
int64_ t pts // 實際這一幀的pts。
int64_t pkt_dts // 對應包當中的dts。
int sample_rate; // 樣本率
uint64_t channel_layout; // 通道類型
int channels; // 通道數量
int format; // 像素格式。區分音頻和視頻。視頻的話就是AVPixelFormat,音頻的話就是AVSampleFormat
二.解碼器解碼代碼演示
// 初始化解封裝 av_register_all(); // 注冊解碼器 avcodec_register_all(); // 初始化網絡 avformat_network_init(); // 打開文件 AVFormatContext *ic = NULL; char path[] = "sdcard/1080.mp4"; // char path[] = "/sdcard/qingfeng.flv"; int ret = avformat_open_input(&ic, path, 0, 0); if (ret != 0) { LOGE("avformat_open_input() called failed: %s", av_err2str(ret)); return env->NewStringUTF(hello.c_str()); } LOGI("avformat_open_input(): File open success."); LOGI("File duration is: %lld, nb_stream is: %d", ic->duration, ic->nb_streams); if (avformat_find_stream_info(ic, 0) >=0 ) { LOGI("File duration is: %lld, nb_stream is: %d", ic->duration, ic->nb_streams); } /**幀率*/ int fps = 0; /*視頻流索引*/ int videoStream = 0; /*音頻流索引*/ int audioStream = 1; // 遍歷獲得音/視頻流索引 for (int i = 0; i < ic->nb_streams; i++) { AVStream *as = ic->streams[i]; if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { LOGI("視頻數據"); videoStream = i; fps = (int)r2d(as->avg_frame_rate); LOGI("fps = %d, width = %d, height = %d, codecid = %d, format = %d", fps, as->codecpar->width, as->codecpar->height, as->codecpar->codec_id, as->codecpar->format); } else if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { LOGI("音頻數據"); audioStream = i; LOGI("sample_rate = %d, channels = %d, sample_format = %d", as->codecpar->sample_rate, as->codecpar->channels, as->codecpar->format ); } } // 也可以利用av_find_best_stream()函數來查找音視頻流索引 // audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); // LOGI("av_find_best_stream, audio index is: %d", audioStream); // 查找視頻解碼器 AVCodec *vCodec = avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id); // 軟解 // vCodec = avcodec_find_decoder_by_name("h264_mediacodec"); // 硬解 if (!vCodec) { LOGE("avcodec_find_decoder() failed. can not found video decoder."); return env->NewStringUTF(hello.c_str()); } // 配置解碼器上下文 AVCodecContext *vc = avcodec_alloc_context3(vCodec); // 將AVStream里面的參數復制到上下文當中 avcodec_parameters_to_context(vc, ic->streams[videoStream]->codecpar); vc->thread_count = 8; // 打開解碼器 ret = avcodec_open2(vc, vCodec, 0); if (ret != 0) { LOGE("avcodec_open2() failed. can not open video decoder, line is: %d", __LINE__); return env->NewStringUTF(hello.c_str()); } // 查找音頻解碼器 AVCodec *aCodec = avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id); // 軟解 // aCodec= avcodec_find_decoder_by_name("h264_mediacodec"); // 硬解 if (!aCodec) { LOGE("avcodec_find_decoder() failed. can not found audio decoder."); return env->NewStringUTF(hello.c_str()); } // 配置解碼器上下文 AVCodecContext *ac = avcodec_alloc_context3(aCodec); // 將AVStream里面的參數復制到上下文當中 avcodec_parameters_to_context(ac, ic->streams[audioStream]->codecpar); ac->thread_count = 8; // 打開解碼器 ret = avcodec_open2(ac, aCodec, 0); if (ret != 0) { LOGE("avcodec_open2() failed. can not open audio decoder"); return env->NewStringUTF(hello.c_str()); } // 讀取幀數據 AVPacket *packet = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); int64_t start = getNowMs(); int frameCount = 0; for (;;) { if (getNowMs() - start >= 3000) { LOGI("now decoder fps is: %d", frameCount / 3); start = getNowMs(); frameCount = 0; } int ret = av_read_frame(ic, packet); if (ret != 0) { LOGE("讀取到結尾處"); int pos = 20 * r2d(ic->streams[videoStream]->time_base); // 改變播放進度 av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME); continue; } // LOGI("Read a Packet. streamIndex=%d, size=%d, pts=%lld, flag=%d", // packet->stream_index, // packet->size, // packet->pts, // packet->flags // ); AVCodecContext *cc = vc; if (packet->stream_index == audioStream) cc = ac; // 發送到線程中去解碼(將packet寫入到解碼隊列當中去) ret = avcodec_send_packet(cc, packet); // 清理 int p = packet->pts; av_packet_unref(packet); if (ret != 0) { // LOGE("avcodec_send_packet failed."); continue; } for(;;) { // 從已解碼成功的數據當中取出一個frame, 要注意send一次,receive不一定是一次 ret = avcodec_receive_frame(cc, frame); if (ret != 0) { break; } if (cc == vc) { frameCount++; } // LOGI("Receive a frame........."); } } // 關閉上下文 avformat_close_input(&ic); return env->NewStringUTF(hello.c_str());
另外:硬件解碼器需要進行注冊,即需要把Java虛擬機的環境傳遞給FFmpeg,因此還需要添加下列代碼,否則解碼器無法打開。
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *res) { av_jni_set_java_vm(vm,0); return JNI_VERSION_1_4; }
av_jni_set_java_vm(vm,0); 包含在 <libavcodec/jni.h> 頭文件當中,不要忘記包含該頭文件。