本文通俗易懂地介绍了电影文件的基本组件,包括容器、流、编解码器和数据包,并通过ffmpeg示例展示了如何处理视频和音频流。文章详细解析了从文件打开到编解码器初始化、数据读取、帧转换及保存的完整流程,特别突出了临界区在数据处理中的重要性。
—
# 电影文件的基本组件:容器、流、编解码器与数据包
电影文件由几个核心组件构成,理解这些组件对于处理多媒体数据至关重要。本文将带你逐一认识这些元素,并通过实例说明如何使用ffmpeg进行操作。
## 基本组件解析
### 容器
电影文件的第一层结构是容器,它决定了文件中数据的位置和格式。常见的容器格式包括AVI和QuickTime。容器本身不处理数据,只是存储和组织数据流。
### 流
容器内包含多个流,常见的有音频流和视频流。流是按时间顺序排列的数据元素集合。每个流由特定的编解码器编码和解码。
### 帧与数据包
流中的数据元素称为帧,而帧则由更小的数据单元——数据包组成。数据包包含解码成原始帧所需的所有信息。在音频流中,一个数据包可能包含多个帧。
### 编解码器
编解码器(CODEC)定义了数据如何被编码和解码。例如,DivX和MP3都是常见的编解码器。编解码器是处理多媒体数据的关键,因为它决定了数据如何被读取和渲染。
## 处理视频和音频流
处理视频和音频流的基本步骤可以简化为以下流程:
1. 打开文件:初始化库并打开文件。
2. 读取流信息:获取文件中的流数据。
3. 找到编解码器:确定流的编解码器并打开它。
4. 读取数据包:从流中读取数据包并解码成帧。
5. 转换和保存:将帧转换为所需格式并保存。
### 代码示例
以下是一个简化的流程示例,展示了如何使用ffmpeg处理视频流:
“`cpp
10 OPEN video_stream FROM video.avi
20 READ packet FROM video_stream INTO frame
30 IF frame NOT COMPLETE GOTO 20
40 DO SOMETHING WITH frame
50 GOTO 20
“`
### 初始化库
在使用ffmpeg之前,需要初始化库:
“`cpp int main(int argc, char argv[]) { `av_register_all()`函数注册所有可用的文件格式和编解码器,确保ffmpeg能自动识别和处理不同格式的文件。 ### 打开文件 打开文件需要指定文件路径和格式: “`cpp ### 读取流信息 获取文件中的流信息: “`cpp ### 编解码器上下文 获取视频流的编解码器上下文: “`cpp ### 打开编解码器 找到并打开编解码器: “`cpp if (avcodec_open2(pCodecCtx, pCodec) < 0)
return -1; // 编解码器打开失败
```
### 分配帧
为帧分配内存:
```cpp
AVFrame pFrame = av_frame_alloc();
AVFrame pFrameRGB = av_frame_alloc();
if (pFrameRGB == NULL)
return -1;
```
### 分配缓冲区
为转换后的帧分配缓冲区:
```cpp
uint8_t buffer = NULL;
int numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); ### 关联帧与缓冲区 将帧与缓冲区关联: “`cpp ### 读取并解码数据包 从流中读取数据包并解码: “`cpp sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, while (av_read_frame(pFormatCtx, &packet) >= 0) { ### 保存帧 将帧保存为PPM文件: “`cpp sprintf(szFilename, “frame%d.ppm”, iFrame); fprintf(pFile, “P6n%d %dn255n”, width, height); fclose(pFile); ### 清理资源 释放分配的资源: “`cpp ## 编译与运行 在Linux或类Unix系统上,使用以下命令编译程序: “`bash 如果使用旧版本的ffmpeg,可能需要移除`-lavutil`: “`bash 编译完成后,大多数图像查看器都能打开生成的PPM文件,方便你查看处理结果。 — {1、电影文件, 2、容器, 3、流, 4、编解码器, 5、数据包, 6、ffmpeg, 7、临界区, 8、多媒体处理, 9、视频流, 10、音频流} 本文是基于《概观电影文件的基本组件:容器、流、编解码器与数据包?》的AI重写版本
#include
av_register_all();
// 后续操作…
}
“`
AVFormatContext pFormatCtx = NULL;
if (avformat_open_input(&pFormatCtx, argv[1], NULL, 0, NULL) != 0)
return -1; // 文件打开失败
“`
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
return -1; // 无法获取流信息
```
### 找到视频流
遍历流,找到第一个视频流:
```cpp
int videoStream = -1;
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
if (videoStream == -1)
return -1; // 未找到视频流
“`
AVCodecContext pCodecCtx = pFormatCtx->streams[videoStream]->codec;
“`
AVCodec pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
fprintf(stderr, “不支持的编解码器n”);
return -1;
}
buffer = (uint8_t )av_malloc(numBytes sizeof(uint8_t));
“`
avpicture_fill((AVPicture )pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
“`
struct SwsContext sws_ctx = NULL;
int frameFinished;
AVPacket packet;
pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24,
SWS_BILINEAR, NULL, NULL, NULL);
if (packet.stream_index == videoStream) {
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (frameFinished) {
sws_scale(sws_ctx, (uint8_t const const )pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
}
}
av_free_packet(&packet);
}
“`
void SaveFrame(AVFrame pFrame, int width, int height, int iFrame) {
FILE pFile;
char szFilename[32];
pFile = fopen(szFilename, “wb”);
if (pFile == NULL)
return;
for (int y = 0; y < height; y++)
fwrite(pFrameRGB->data[0] + y pFrameRGB->linesize[0], 1, width 3, pFile);
}
“`
av_free(buffer);
av_free(pFrameRGB);
av_free(pFrame);
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);
avformat_close_input(&pFormatCtx);
“`
gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm
“`
gcc -o tutorial01 tutorial01.c -lavformat -lavcodec -lz -lm
“`
评论(0)