本文共 9099 字,大约阅读时间需要 30 分钟。
当我们在播放一个多媒体文件时,实际上需要经过几个关键步骤才能将压缩编码的数据转化为可显示和可听的形式。这些步骤主要包括解封装、解码以及音视频同步等环节。
在多媒体文件的播放过程中,首先需要对封装格式的文件进行解封装操作。这一步骤的作用是将压缩编码的音视频数据分离成音频流和视频流。常见的封装格式有MP4、MKV、RMVB、TS、FLV、AVI等。解封装后,我们可以得到具体的音视频压缩编码数据。
解码是整个多媒体文件播放过程中最核心的环节。通过解码,我们可以将压缩编码的音视频数据转化为非压缩的原始数据。例如,视频数据会被解码为YUV或RGB格式,而音频数据则会被解码为PCM格式。
在解封装和解码完成后,需要对解码出的音频和视频数据进行同步。根据解封装过程中获取到的参数信息,我们可以确保音视频数据的播放时序一致,从而实现音视频的同步显示和播放。
使用FFMPEG进行音视频解码时,我们需要对媒体文件进行以下几个关键步骤的操作。
在使用FFMPEG解码多媒体文件之前,需要先注册所有的FFMPEG组件。通过调用av_register_all()函数,我们可以注册所有支持的容器和对应的编解码器。
av_register_all();
接下来,我们需要通过avformat_alloc_context()函数获取一个媒体文件处理的上下文。然后,使用avformat_open_input()函数打开输入的多媒体文件。
AVFormatContext *pFormatCtx = avformat_alloc_context();avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL);
通过avformat_find_stream_info()函数,我们可以获取多媒体文件的详细信息,包括视频和音频流的数量、类型等。
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { printf("无法获取视频文件信息"); return;} 在多媒体文件中,我们可能包含多个音视频流。通过遍历pFormatCtx->streams数组,我们可以找到对应的音视频流索引。
int video_stream_idx = -1;int i = 0;for (; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_idx = i; break; }} 通过获取视频流的解码器上下文,我们可以调用avcodec_find_decoder()函数查找对应的解码器。
AVCodecContext *pCodecCtx = pFormatCtx->streams[video_stream_idx]->codec;AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
接下来,我们需要初始化解码器,并将解码器与媒体文件处理上下文绑定。
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { printf("解码器无法打开"); return;} 通过调用avcodec_decode_video2()函数,我们可以逐帧解码视频数据,并将解码后的像素数据存储在AVFrame对象中。
while (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == video_stream_idx) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) { printf("解码错误"); return; } if (got_picture) { sws_scale(swsCtx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); // 将解码后的像素数据写入文件 fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); frame_count++; printf("解码第%d帧\n", frame_count); } } av_free_packet(packet);} 对于音频解码,我们可以使用类似的方法来实现。通过遍历音频流的索引,并调用avcodec_decode_audio4()函数,我们可以逐帧解码音频数据。
while (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == audio_stream_idx) { ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet); if (ret < 0) { printf("无法解码"); return; } if (got_frame) { // 重采样处理 swr_convert(swrCtx, out_buffer, 2 * 44100, frame->data, frame->nb_samples); // 将解码后的音频数据写入文件 fwrite(out_buffer, 1, out_buffer_size, fp_pcm); framecount++; printf("解码第%d帧\n", framecount); } } av_free_packet(packet);} #include#include #include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"int main { const char *input = "test.mp4"; const char *output = "test.yuv"; av_register_all(); AVFormatContext *pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) { printf("无法打开输入视频文件"); return; } if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { printf("无法获取视频文件信息"); return; } int video_stream_idx = -1; int i = 0; for (; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_idx = i; break; } } if (video_stream_idx == -1) { printf("找不到视频流"); return; } AVCodecContext *pCodecCtx = pFormatCtx->streams[video_stream_idx]->codec; AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if (pCodec == NULL) { printf("找不到解码器"); return; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { printf("解码器无法打开"); return; } AVPacket *packet = av_malloc(sizeof(AVPacket)); AVFrame *pFrame = av_frame_alloc(); AVFrame *pFrameYUV = av_frame_alloc(); uint8_t *out_buffer = av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); FILE *fp_yuv = fopen(output, "wb+"); int frame_count = 0; while (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == video_stream_idx) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) { printf("解码错误"); return; } if (got_picture) { sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); int y_size = pCodecCtx->width * pCodecCtx->height; fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); frame_count++; printf("解码第%d帧\n", frame_count); } } av_free_packet(packet); } fclose(fp_yuv); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_free_context(pFormatCtx);}
#include#include #include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include "libswresample/swresample.h"int main { const char *input = "test.mp3"; const char *output = "out.pcm"; av_register_all(); AVFormatContext *pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) { printf("无法打开输入音频文件"); return; } if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { printf("无法获取音频信息"); return; } int audio_stream_idx = -1; int i = 0; for (; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream_idx = i; break; } } if (audio_stream_idx == -1) { printf("找不到音频流"); return; } AVCodecContext *pCodeCtx = pFormatCtx->streams[audio_stream_idx]->codec; AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id); if (pCodec == NULL) { printf("无法解码"); return; } if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0) { printf("编码器无法打开"); return; } AVPacket *packet = av_malloc(sizeof(AVPacket)); AVFrame *frame = av_frame_alloc(); SwrContext *swrCtx = swr_alloc(); enum AVSampleFormat in_sample_fmt = pCodeCtx->sample_fmt; enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16; int in_sample_rate = pCodeCtx->sample_rate; int out_sample_rate = 44100; uint64_t in_ch_layout = pCodeCtx->channel_layout; uint64_t out_ch_layout = AV_CH_LAYOUT_MONO; swr_alloc_set_opts(swrCtx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL); swr_init(swrCtx); FILE *fp_pcm = fopen(output, "wb+"); int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout); uint8_t *out_buffer = av_malloc(2 * 44100); int ret = 0; int framecount = 0; while (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == audio_stream_idx) { ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet); if (ret < 0) { printf("解码错误"); return; } if (got_frame) { swr_convert(swrCtx, out_buffer, 2 * 44100, frame->data, frame->nb_samples); int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, frame->nb_samples, out_sample_fmt, 1); fwrite(out_buffer, 1, out_buffer_size, fp_pcm); framecount++; printf("解码第%d帧\n", framecount); } } av_free_packet(packet); } fclose(fp_pcm); av_free(out_buffer); swr_free(swrCtx); avcodec_close(pCodeCtx); avformat_close_input(&pFormatCtx); return 0;}
通过以上步骤,我们可以清晰地了解FFMPEG在音视频解码中的工作原理。通过注册FFMPEG组件、初始化媒体文件处理上下文、解封装、解码以及音视频同步等环节,我们可以实现多媒体文件的播放。在实际应用中,可以根据具体需求选择是否进行音频解码和视频解码,以达到最佳的播放效果。
转载地址:http://ayfu.baihongyu.com/