当前位置: 首页 > news >正文

FFmpeg 与 C++ 构建音视频处理全链路实战(一)—— 环境配置与视频解封装

在数字媒体的浩瀚宇宙中,FFmpeg 就像一艘功能强大的星际战舰,承载着处理音视频数据的重任。而 C++ 作为一门高效、灵活的编程语言,犹如一位技艺精湛的星际工程师,能够精准操控 FFmpeg 战舰,完成各类复杂的音视频处理任务。本系列文章将带领你深入探索 FFmpeg 与 C++ 结合进行音视频衍生品开发的全过程,涵盖视频解封装、解码、封装、编码,视频尺寸变化,音频重采样等核心功能,为你揭开数字媒体开发的神秘面纱。

一、FFmpeg 与 C++ 的相遇:开启数字媒体开发之门

FFmpeg 是一套功能强大的开源音视频处理库,它几乎支持市面上所有的音视频格式,能够实现解封装、解码、编码、封装等一系列操作。从常见的 MP4、FLV 视频格式,到复杂的 H.264、H.265 编码标准,FFmpeg 都能轻松应对。而 C++ 语言以其高效的执行效率、强大的内存管理能力和面向对象的特性,成为与 FFmpeg 协作开发的理想选择。通过 C++ 调用 FFmpeg 的 API,我们可以构建出各种音视频处理应用,如视频播放器、视频编辑器、直播推流工具等。

在开始开发之前,我们需要先搭建开发环境,将 FFmpeg 库集成到 C++ 项目中。这就好比为星际工程师配备好专业的工具,以便后续的工作顺利开展。

首先安装qt和Vistual Studio,我使用的是windows11系统,安装的是QT6.7.3和Vistual Studio2022,配置好后进入FFmpeg官网下载编译好的库文件即可。

qt和Vistual Studio安装参考: VS+Qt —Vistual Studio 2022+Qt6安装教程以及解决Qt Vistual Studio Tools下载慢和VS无法打开.ui进行设计的问题FFmpeg下载安装参考:

【Windows】FFmpeg安装教程_ffmpeg win7-CSDN博客

我的ffmpeg直接使用夏曹俊老师提供的工程文档(FFmpeg4.2.1 SDK库): FFmpeg中文网站 中文教程 Android 安卓 @-ffmpeg sdk download 视频课程 中文教程 夏曹俊 老夏课堂

 文件结构如图:

我们打开Vistual Studio,创建一个控制台程序,创建路径设置到上图中的src内部,项目名称设定为firsttest,再firsttest.cpp中输入以下代码:

#include <iostream>
using namespace std;
extern "C"//c语言库c++程序引用
{#include <libavcodec/avcodec.h>
}
//预处理指令导入库
#pragma comment(lib,"avcodec.lib")int main()
{std::cout << "First Test FFmpeg!\n";cout << avcodec_configuration() << endl;getchar();return 0;
}

 再解决方案资源管理器中右击本项目,点击属性:

再常规中设置输出目录如图

 点击调试,设置工作目录,这是为了本地调试时项目能找到动态库。

 再C/C++中设置附加包含目录,让程序能找到头文件

 再链接器中设置库文件路径,对于编译过程中的链接库,由于程序希望能跨平台,这里直接用预处理指令进行库的链接(#pragma comment(lib,"avcodec.lib"))

 设置好后点击本地调试器,输出如下则环境备好了

我们在学习编解码的过程中会一层层剥开视频封装,在这个过程中会产生各种各样格式的文件:aac裸流文件,h264裸流文件,pcm流文件,yuv流文件等等,这时我们就需要一个合适的能够播放各种格式文件的播放器来验证我们所产生的文件是否正确并能够正常播放。

进入vlc官网 :VLC: Official site - Free multimedia solutions for all OS! - VideoLAN

点击下载安装即可。

海康威视YUV播放器:

二、解封装:探索音视频数据的 “包裹”

视频文件就像是一个个精心包装的包裹,里面封装着音频和视频数据。解封装的过程,就是打开这个包裹,将音频和视频数据分别提取出来。在 FFmpeg 中,通过avformat_open_input函数打开视频文件,然后使用avformat_find_stream_info函数获取文件的流信息,包括视频流和音频流的数量、编码格式等。

我们在之前的深入解析 FLV 封装格式:流媒体世界的轻骑兵一文中详细介绍过FLV 文件格式包含了文件头、标签等结构,解封装时需要按照其特定的格式解析出音视频数据。对于 MP4我们之前的文章剥开 MP4 的 千层 “数字洋葱”:从外到内拆解通用媒体容器的核心详细介绍了其封装结构,对于mp4与flv解封装过程和解析方式有所不同。

通过遍历流信息,我们可以找到视频流和音频流,并分别获取它们的相关参数。例如,对于视频流,我们可以获取到视频的宽度、高度、帧率等信息;对于音频流,可以获取到采样率、声道数等信息。这些信息对于后续的处理至关重要,就像星际工程师在执行任务前需要了解目标的详细参数一样。

现在,我们将用代码实现将视频文件解封装,并将其拆解为 AAC 音频文件和 H.264 视频文件,下面结合具体代码,深入剖析每一行代码的功能与作用:

(一)文件与变量初始化

const char* in_filename = "baseball.mp4";
const char* aac_filename = "baseball.aac";
const char* h264_filename = "baseball.h264";
FILE* aac_fd = NULL;
FILE* h264_fd = NULL;AVFormatContext* ifmt_ctx = NULL;
AVPacket* pkt = NULL;int video_index = -1;
int audio_index = -1;int ret = 0;
char errors[ERROR_STRING_SIZE + 1];

首先定义了输入的 MP4 文件名in_filename,以及输出的 AAC 音频文件名aac_filename和 H.264 视频文件名h264_filename。接着声明了用于操作文件的文件指针aac_fdh264_fd,以及 FFmpeg 中用于存储媒体文件格式信息的AVFormatContext指针ifmt_ctx,和用于存储音视频数据的AVPacket指针pktvideo_indexaudio_index用于记录视频流和音频流在文件中的索引,ret用于存储函数调用的返回值,errors数组用于存储错误信息 

(二)打开输入文件与获取流信息

ret = fopen_s(&aac_fd,aac_filename, "wb");
if (ret < 0)
{printf("fopen %s failed\n", h264_filename);return -1;
}
ret = fopen_s(&h264_fd, h264_filename, "wb");
if (ret < 0)
{printf("fopen %s failed\n", h264_filename);return -1;
}ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);
if (ret < 0)
{av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avformat_open_input failed:%d\n", ret);printf("avformat_open_input failed:%s\n", errors);avformat_close_input(&ifmt_ctx);return - 1;
}ret = avformat_find_stream_info(ifmt_ctx, NULL);
if (ret < 0)
{av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avformat_find_stream_info failed:%d\n", ret);printf("avformat_find_stream_info failed:%s\n", errors);avformat_close_input(&ifmt_ctx);return -1;
}

使用fopen_s函数分别打开用于写入 AAC 音频和 H.264 视频的文件,如果打开失败则输出错误信息并退出程序。

然后调用avformat_open_input函数打开输入的 MP4 文件,并将文件格式信息存储到ifmt_ctx中。如果打开失败,使用av_strerror获取错误信息并打印,关闭输入上下文后退出。接着调用avformat_find_stream_info函数获取文件中的流信息,同样在失败时进行错误处理。

 (三)获取视频流和音频流索引

video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_index == -1) {printf("av_find_best_stream video_index failed\n");avformat_close_input(&ifmt_ctx);return -1;
}
std::cout << "video_index = " << video_index << std::endl;
audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_index == -1) {printf("av_find_best_stream audio_index failed\n");avformat_close_input(&ifmt_ctx);return -1;
}
std::cout << "audio_index = " << audio_index << std::endl;

av_find_best_stream是ffmpeg新版本引入的函数,该函数用于分别查找视频流和音频流的索引。该函数会在文件的流信息中搜索指定类型(视频或音频)的最佳流,并返回其索引。如果查找失败,输出错误信息,关闭输入上下文后退出。找到索引后,将其打印到控制台,方便我们查看。

在ffmpeg就版本中,可以使用以下方法查找流索引并打印音视频流关键信息:

for (size_t i = 0; i < ifmt_ctx->nb_streams; i++)
{AVStream* in_stream = ifmt_ctx->streams[i];if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){std::cout << "=========================Audio inf=========================" << std::endl;audio_index = i;std::cout << "audio_index = " << audio_index << std::endl;std::cout << "Sample Rate: " << in_stream->codecpar->sample_rate << std::endl;if (in_stream->codecpar->format == AV_SAMPLE_FMT_FLTP){std::cout << "sampleformat:AV_SAMPLE_FMT_FLTP" << std::endl;}else if (in_stream->codecpar->format == AV_SAMPLE_FMT_S16P){std::cout << "sampleformat:AV_SAMPLE_FMT_S16P" << std::endl;}std::cout << "channles: " << in_stream->codecpar->channels << std::endl;if (in_stream->codecpar->codec_id == AV_CODEC_ID_AAC){std::cout << "audio codec:AAC" << std::endl;}else if (in_stream->codecpar->codec_id == AV_CODEC_ID_MP3){std::cout << "audio codec:MP3" << std::endl;}else{std::cout << "audio codec_id: " << in_stream->codecpar->codec_id << std::endl;}if (in_stream->duration != AV_NOPTS_VALUE){int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);printf("audio duration: %02d:%02d:%02d\n",duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));}else{printf("audio duration unknown");}}else if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){std::cout << "=========================Video inf=========================" << std::endl;video_index = i;std::cout << "video_index = " << video_index << std::endl;std::cout << "fps: " << av_q2d(in_stream->avg_frame_rate) << std::endl;std::cout << "width: " << in_stream->codecpar->width << " height: " << in_stream->codecpar->height << std::endl;if (in_stream->codecpar->codec_id == AV_CODEC_ID_MPEG4){std::cout << "video codec:MPEG4" << std::endl;}else if (in_stream->codecpar->codec_id == AV_CODEC_ID_H264){std::cout << "video codec:H264" << std::endl;}else{std::cout << "video codec: " << in_stream->codecpar->codec_id << std::endl;}if (in_stream->duration != AV_NOPTS_VALUE){int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);printf("video duration: %02d:%02d:%02d\n",duration_video / 3600,(duration_video % 3600) / 60,(duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上}else{printf("video duration unknown");}}
}

(四)初始化 H.264 比特流过滤器

const AVBitStreamFilter* bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
if (!bsfilter) {avformat_close_input(&ifmt_ctx);printf("av_bsf_get_by_name h264_mp4toannexb failed\n");return -1;
}
AVBSFContext* bsf_ctx = NULL;
ret = av_bsf_alloc(bsfilter, &bsf_ctx);
if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_alloc failed:%s\n", errors);avformat_close_input(&ifmt_ctx);return -1;
}
ret = avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[video_index]->codecpar);
if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avcodec_parameters_copy failed:%s\n", errors);avformat_close_input(&ifmt_ctx);av_bsf_free(&bsf_ctx);return -1;
}
ret = av_bsf_init(bsf_ctx);
if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_init failed:%s\n", errors);avformat_close_input(&ifmt_ctx);av_bsf_free(&bsf_ctx);return -1;
}
std::cout << "AVBitStreamFilter init done!" << std::endl;

由于从 MP4 文件中提取的 H.264 视频流格式需要转换为符合标准的 Annex B 格式(添加 SPS/PPS 等信息),所以这里初始化了一个 H.264 比特流过滤器。首先通过av_bsf_get_by_name获取名为h264_mp4toannexb的比特流过滤器,然后使用av_bsf_alloc分配过滤器上下文,将输入视频流的编码参数复制到过滤器上下文中,并调用av_bsf_init初始化过滤器。每一步操作都进行了错误处理,确保过滤器正确初始化。

 (五)读取并处理音视频数据包

pkt = av_packet_alloc();
av_init_packet(pkt);
while (1)
{ret = av_read_frame(ifmt_ctx, pkt);if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_read_frame failed:%s\n", errors);break;}if (pkt->stream_index == audio_index){// 处理音频char adts_header_buf[7] = { 0 };adts_header(adts_header_buf, pkt->size,ifmt_ctx->streams[audio_index]->codecpar->profile,ifmt_ctx->streams[audio_index]->codecpar->sample_rate,ifmt_ctx->streams[audio_index]->codecpar->channels);fwrite(adts_header_buf, 1, 7, aac_fd);size_t size = fwrite(pkt->data, 1, pkt->size, aac_fd);if (size != pkt->size){av_log(NULL, AV_LOG_DEBUG, "aac warning, length of writed data isn't equal pkt->size(%d, %d)\n",size,pkt->size);}av_packet_unref(pkt);}else if (pkt->stream_index == video_index){// 处理视频ret = av_bsf_send_packet(bsf_ctx, pkt);if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_send_packet failed:%s\n", errors);av_packet_unref(pkt);continue;}while (1) {ret = av_bsf_receive_packet(bsf_ctx, pkt);if (ret != 0) {break;}size_t size = fwrite(pkt->data, 1, pkt->size, h264_fd);if (size != pkt->size){av_log(NULL, AV_LOG_DEBUG, "h264 warning, length of writed data isn't equal pkt->size(%d, %d)\n",size,pkt->size);}av_packet_unref(pkt);}}else{av_packet_unref(pkt);}
}

首先使用av_packet_allocav_init_packet分配并初始化一个AVPacket对象,用于存储读取到的音视频数据包。

在一个无限循环中,调用av_read_frame从输入文件中读取一个数据包。如果读取失败,获取错误信息并退出循环。

当读取到的数据包的流索引与音频流索引相同时,调用adts_header函数(具体实现见附录)生成 ADTS 头部信息,将头部信息和音频数据写入 AAC 输出文件。如果写入的数据长度与数据包长度不一致,打印警告信息。最后使用av_packet_unref释放数据包的引用计数。

当数据包的流索引与视频流索引相同时,将数据包发送到 H.264 比特流过滤器进行格式转换,然后通过循环接收转换后的数据包,并将其写入 H.264 输出文件。同样在写入长度不一致时打印警告信息,处理完后释放数据包。

对于其他流索引的数据包,直接释放引用计数。

(六)资源释放与程序结束

if (aac_fd)fclose(aac_fd);
if (h264_fd)fclose(h264_fd);
if (bsf_ctx)av_bsf_free(&bsf_ctx);
if (pkt)av_packet_free(&pkt);
if (ifmt_ctx)avformat_close_input(&ifmt_ctx);
getchar();
return 0;

在程序结束前,依次关闭打开的 AAC 和 H.264 文件,释放比特流过滤器上下文、数据包和输入格式上下文占用的资源,确保没有内存泄漏和文件资源未关闭的情况。最后通过getchar()等待用户输入(防止控制台窗口一闪而过),然后返回 0 表示程序正常结束。

(七)打印结果并测试输出文件

进入项目文件夹下的bin\win64下找到长生的AAC和H.264文件,用VLC播放测试:

H.264测试

附录

ADTS格式可以参考:深入探索 AAC 编码原理与 ADTS 格式:音频世界的智慧结晶-CSDN博客

完整代码如下:

#include <iostream>
#include <stdio.h>
extern "C"
{#include <libavformat/avformat.h>
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")#define ADTS_HEADER_LEN  7
#define ERROR_STRING_SIZE 1024const int sampling_frequencies[] = {96000,  // 0x088200,  // 0x164000,  // 0x248000,  // 0x344100,  // 0x432000,  // 0x524000,  // 0x622050,  // 0x716000,  // 0x812000,  // 0x911025,  // 0xa8000   // 0xb// 0xc d e f是保留的
};int adts_header(char* const p_adts_header, const int data_length,const int profile, const int samplerate,const int channels)
{int sampling_frequency_index = 3; // 默认使用48000hzint adtsLen = data_length + 7;int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);int i = 0;for (i = 0; i < frequencies_size; i++){if (sampling_frequencies[i] == samplerate){sampling_frequency_index = i;break;}}if (i >= frequencies_size){printf("unsupport samplerate:%d\n", samplerate);return -1;}p_adts_header[0] = 0xff;         //syncword:0xfff                          高8bitsp_adts_header[1] = 0xf0;         //syncword:0xfff                          低4bitsp_adts_header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bitp_adts_header[1] |= (0 << 1);    //Layer:0                                 2bitsp_adts_header[1] |= 1;           //protection absent:1                     1bitp_adts_header[2] = (profile) << 6;            //profile:profile               2bitsp_adts_header[2] |= (sampling_frequency_index & 0x0f) << 2; //sampling frequency index:sampling_frequency_index  4bitsp_adts_header[2] |= (0 << 1);             //private bit:0                   1bitp_adts_header[2] |= (channels & 0x04) >> 2; //channel configuration:channels  高1bitp_adts_header[3] = (channels & 0x03) << 6; //channel configuration:channels 低2bitsp_adts_header[3] |= (0 << 5);               //original:0                1bitp_adts_header[3] |= (0 << 4);               //home:0                    1bitp_adts_header[3] |= (0 << 3);               //copyright id bit:0        1bitp_adts_header[3] |= (0 << 2);               //copyright id start:0      1bitp_adts_header[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bitsp_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bitsp_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bitsp_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bitsp_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits// number_of_raw_data_blocks_in_frame://    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。return 0;
}int main(int argc, char* argv[])
{const char* in_filename = "baseball.mp4";const char* aac_filename = "baseball.aac";const char* h264_filename = "baseball.h264";FILE* aac_fd = NULL;FILE* h264_fd = NULL;AVFormatContext* ifmt_ctx = NULL;AVPacket* pkt = NULL;int video_index = -1;int audio_index = -1;int ret = 0;char errors[ERROR_STRING_SIZE + 1];ret = fopen_s(&aac_fd,aac_filename, "wb");if (ret < 0){printf("fopen %s failed\n", h264_filename);return -1;}ret = fopen_s(&h264_fd, h264_filename, "wb");if (ret < 0){printf("fopen %s failed\n", h264_filename);return -1;}ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);if (ret < 0){av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avformat_open_input failed:%d\n", ret);printf("avformat_open_input failed:%s\n", errors);avformat_close_input(&ifmt_ctx);return - 1;}ret = avformat_find_stream_info(ifmt_ctx, NULL);if (ret < 0){av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avformat_find_stream_info failed:%d\n", ret);printf("avformat_find_stream_info failed:%s\n", errors);avformat_close_input(&ifmt_ctx);return -1;}std::cout << "===========================dump information begin==============================" << std::endl;av_dump_format(ifmt_ctx, 0, in_filename, 0);std::cout << "============================dump information end===============================" << std::endl;std::cout << "stream_nums: " << ifmt_ctx->nb_streams << std::endl;std::cout << "average ratios: " << ifmt_ctx->bit_rate << std::endl;std::cout << "video codec id: " << ifmt_ctx->video_codec_id << std::endl;std::cout << "audio codec id: " << ifmt_ctx->audio_codec_id << std::endl;int total_seconds, hours, mins, secs;total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;hours = total_seconds / 3600;mins = (total_seconds % 3600) / 60;secs = (total_seconds % 60);std::cout << "total duration: " << hours << ":" << mins << ":" << secs << std::endl;//查找流索引方法1video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if (video_index == -1) {printf("av_find_best_stream video_index failed\n");avformat_close_input(&ifmt_ctx);return -1;}std::cout << "video_index = " << video_index << std::endl;audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (audio_index == -1) {printf("av_find_best_stream audio_index failed\n");avformat_close_input(&ifmt_ctx);return -1;}std::cout << "audio_index = " << audio_index << std::endl;//查找流索引方法2for (size_t i = 0; i < ifmt_ctx->nb_streams; i++){AVStream* in_stream = ifmt_ctx->streams[i];if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){std::cout << "=========================Audio inf=========================" << std::endl;audio_index = i;std::cout << "audio_index = " << audio_index << std::endl;std::cout << "Sample Rate: " << in_stream->codecpar->sample_rate << std::endl;if (in_stream->codecpar->format == AV_SAMPLE_FMT_FLTP){std::cout << "sampleformat:AV_SAMPLE_FMT_FLTP" << std::endl;}else if (in_stream->codecpar->format == AV_SAMPLE_FMT_S16P){std::cout << "sampleformat:AV_SAMPLE_FMT_S16P" << std::endl;}std::cout << "channles: " << in_stream->codecpar->channels << std::endl;if (in_stream->codecpar->codec_id == AV_CODEC_ID_AAC){std::cout << "audio codec:AAC" << std::endl;}else if (in_stream->codecpar->codec_id == AV_CODEC_ID_MP3){std::cout << "audio codec:MP3" << std::endl;}else{std::cout << "audio codec_id: " << in_stream->codecpar->codec_id << std::endl;}if (in_stream->duration != AV_NOPTS_VALUE){int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);printf("audio duration: %02d:%02d:%02d\n",duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));}else{printf("audio duration unknown");}}else if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){std::cout << "=========================Video inf=========================" << std::endl;video_index = i;std::cout << "video_index = " << video_index << std::endl;std::cout << "fps: " << av_q2d(in_stream->avg_frame_rate) << std::endl;std::cout << "width: " << in_stream->codecpar->width << " height: " << in_stream->codecpar->height << std::endl;if (in_stream->codecpar->codec_id == AV_CODEC_ID_MPEG4){std::cout << "video codec:MPEG4" << std::endl;}else if (in_stream->codecpar->codec_id == AV_CODEC_ID_H264){std::cout << "video codec:H264" << std::endl;}else{std::cout << "video codec: " << in_stream->codecpar->codec_id << std::endl;}if (in_stream->duration != AV_NOPTS_VALUE){int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);printf("video duration: %02d:%02d:%02d\n",duration_video / 3600,(duration_video % 3600) / 60,(duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上}else{printf("video duration unknown");}}}// 1 获取相应的比特流过滤器//FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。// FLV封装时,可以把多个NALU放在一个VIDEO TAG中,结构为4B NALU长度+NALU1+4B NALU长度+NALU2+...,// 需要做的处理把4B长度换成00000001或者000001const AVBitStreamFilter* bsfilter = av_bsf_get_by_name("h264_mp4toannexb");      // 对应面向对象的方法if (!bsfilter) {avformat_close_input(&ifmt_ctx);printf("av_bsf_get_by_name h264_mp4toannexb failed\n");return -1;}AVBSFContext* bsf_ctx = NULL;        // 对应面向对象的变量ret = av_bsf_alloc(bsfilter, &bsf_ctx);if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_alloc failed:%s\n", errors);avformat_close_input(&ifmt_ctx);return -1;}ret = avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[video_index]->codecpar);if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("avcodec_parameters_copy failed:%s\n", errors);avformat_close_input(&ifmt_ctx);av_bsf_free(&bsf_ctx);return -1;}ret = av_bsf_init(bsf_ctx);if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_init failed:%s\n", errors);avformat_close_input(&ifmt_ctx);av_bsf_free(&bsf_ctx);return -1;}std::cout << "AVBitStreamFilter init done!" << std::endl;pkt = av_packet_alloc();av_init_packet(pkt);while (1){std::cout << "*" << std::endl;ret = av_read_frame(ifmt_ctx, pkt);if (ret < 0) {av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_read_frame failed:%s\n", errors);break;}if (pkt->stream_index == audio_index){// 处理音频char adts_header_buf[7] = { 0 };adts_header(adts_header_buf, pkt->size,ifmt_ctx->streams[audio_index]->codecpar->profile,ifmt_ctx->streams[audio_index]->codecpar->sample_rate,ifmt_ctx->streams[audio_index]->codecpar->channels);fwrite(adts_header_buf, 1, 7, aac_fd);  // 写adts header , ts流不适用,ts流分离出来的packet带了adts headersize_t size = fwrite(pkt->data, 1, pkt->size, aac_fd);   // 写adts dataif (size != pkt->size){av_log(NULL, AV_LOG_DEBUG, "aac warning, length of writed data isn't equal pkt->size(%d, %d)\n",size,pkt->size);}av_packet_unref(pkt);}else if (pkt->stream_index == video_index){// 处理视频ret = av_bsf_send_packet(bsf_ctx, pkt); // 内部把我们传入的buf转移到自己bsf内部if (ret < 0) {       // 基本不会进入该逻辑av_strerror(ret, errors, ERROR_STRING_SIZE);printf("av_bsf_send_packet failed:%s\n", errors);av_packet_unref(pkt);continue;}//            av_packet_unref(pkt); // 这里不需要去释放内存while (1) {ret = av_bsf_receive_packet(bsf_ctx, pkt);if (ret != 0) {break;}size_t size = fwrite(pkt->data, 1, pkt->size, h264_fd);if (size != pkt->size){av_log(NULL, AV_LOG_DEBUG, "h264 warning, length of writed data isn't equal pkt->size(%d, %d)\n",size,pkt->size);}av_packet_unref(pkt);}}else{av_packet_unref(pkt);}}if (aac_fd)fclose(aac_fd);if (h264_fd)fclose(h264_fd);if (bsf_ctx)av_bsf_free(&bsf_ctx);if (pkt)av_packet_free(&pkt);if (ifmt_ctx)avformat_close_input(&ifmt_ctx);getchar();return 0;
}

相关文章:

FFmpeg 与 C++ 构建音视频处理全链路实战(一)—— 环境配置与视频解封装

在数字媒体的浩瀚宇宙中&#xff0c;FFmpeg 就像一艘功能强大的星际战舰&#xff0c;承载着处理音视频数据的重任。而 C 作为一门高效、灵活的编程语言&#xff0c;犹如一位技艺精湛的星际工程师&#xff0c;能够精准操控 FFmpeg 战舰&#xff0c;完成各类复杂的音视频处理任务…...

什么是 NoSQL 数据库?它与关系型数据库 (RDBMS) 的主要区别是什么?

我们来详细分析一下 NoSQL 数据库与关系型数据库 (RDBMS) 的主要区别。 什么是 NoSQL 数据库&#xff1f; NoSQL (通常指 “Not Only SQL” 而不仅仅是 “No SQL”) 是一类数据库管理系统的总称。它们的设计目标是解决传统关系型数据库 (RDBMS) 在某些场景下的局限性&#xf…...

AI需求分析话术 | DeepSeek R1

运行环境&#xff1a;jupyter notebook (python 3.12.7) Dash 场景&#xff1a; 收集了小程序的问题点和优化建议&#xff0c;一键AI分析&#xff0c;快速排优先级 指令话术&#xff1a; 对收集的小程序问题点和建议&#xff0c;做需求分析并总结形成报告&#xff0c;报告结构…...

【Redis】键值对数据库实现

目录 1、背景2、五种基本数据类型对应底层实现3、redis数据结构 1、背景 redis是一个&#xff08;key-value&#xff09;键值对数据库&#xff0c;其中value可以是五大基本数据类型&#xff1a;string、list、hash、set、zset&#xff0c;这五大基本数据类型对应着不同的底层结…...

MySQL 8.0 OCP 英文题库解析(三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题16~25 试题16:…...

互联网大厂Java求职面试:优惠券服务架构设计与AI增强实践-1

互联网大厂Java求职面试&#xff1a;优惠券服务架构设计与AI增强实践-1 在一间简洁明亮的会议室里&#xff0c;郑薪苦正面对着一位技术总监级别的面试官&#xff0c;这位面试官拥有超过十年的大型互联网企业经验&#xff0c;以技术全面性与落地能力著称。 第一轮面试&#xf…...

object的常用方法

在面向对象编程中&#xff0c;Object 类是所有类的根类&#xff0c;它提供了一些基本的方法&#xff0c;这些方法可以被所有对象继承和使用。以下是一些在 Java 中 Object 类的常用方法&#xff0c;以及它们的作用和使用示例&#xff1a; 1. equals(Object obj) 作用&#xff…...

解决vue create 创建项目,不能使用上下键选择模板的问题

使用 git bash 创建vue项目时候&#xff0c;无法使用上下键盘按键选择创建模板 处理&#xff1a; 1.当前界面&#xff0c;按CTR C终止创建命令&#xff1b; 2.使用 alias vuewinpty vue.cmd&#xff0c;更新命令环境&#xff1b; 3.再次使用 vue create demo创建项目&#xf…...

AI Agent开发第64课-DIFY和企业现有系统结合实现高可配置的智能零售AI Agent(上)

开篇 我们之前花了将近10个篇章讲Dify的一些基础应用,包括在讲Dify之前我们讲到了几十个AI Agent的开发例子,我不知道大家发觉了没有,在AI Agent开发过程中我们经常会伴随着这样的一些问题: 需要经常改猫娘;需要经常改调用LLM的参数,甚至在一个流程中有3个节点,每个节点…...

3.Redis-set集合类型

1.用集合做差集、并集&#xff08;共同关注&#xff09;、交集...

软考 系统架构设计师系列知识点之杂项集萃(57)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;56&#xff09; 第93题 美国著名的卡内基梅隆大学软件工程学研究所针对软件工程的工程管理能力与水平进行了充分研究&#xff0c;提出了5级管理能力的模式&#xff0c;包括临时凑合阶段、简单模仿…...

Cabot:开源免费的 PagerDuty 替代品,让系统监控更简单高效

在当今复杂的IT环境中,及时发现并解决系统问题至关重要。而Cabot作为一款开源免费的监控工具,为开发和运维团队提供了强大而简单的解决方案。本文将详细介绍Cabot的核心功能、优势以及快速部署方法,帮助你更好地保障系统稳定性。 Cabot简介 Cabot是一个功能类似PagerDuty的开…...

AI中的MCP是什么?MCP的作用及未来方向预测 (使用go-zero 快速搭建MCP服务器)

AI是当下最热的风。在当今AI技术飞速发展的时代&#xff0c;AI的应用已经渗透到我们日常生活的方方面面。然而&#xff0c;随着AI系统的复杂性不断增加&#xff0c;如何让AI具备更强的自主性和灵活性成为了业界关注的焦点。这就引出了Model Context Protocol&#xff08;MCP&am…...

字节开源FlowGram与n8n 技术选型

字节跳动开源的 FlowGram 和 n8n 是两款功能强大但定位不同的工作流编排工具&#xff0c;以下是两者的技术选型对比分析&#xff0c;结合其核心特性、适用场景和优劣势&#xff1a; 一、核心特性对比 维度FlowGram&#xff08;字节开源&#xff09;n8n定位面向AI场景的可视化工…...

面试专栏-03-Git的常用命令

二、Git常用命令学习 git本质上&#xff0c;就是一个 git类型的文件夹 1、基础配置信息 git -v&#xff1a;查看 git 版本信息 git config --global user.name "dz.cn"&#xff1a;配置用户名&#xff0c;注意&#xff0c;这里配置的用户名在进行版本提交时&#xf…...

使用 Syncthing 在两台电脑之间同步文件:简单教程

&#x1f9e9; 什么是 Syncthing&#xff1f; Syncthing 是一个开源、跨平台、点对点的文件同步工具&#xff0c;类似于 Dropbox&#xff0c;但不依赖第三方服务器。它直接在你的设备之间同步文件&#xff0c;更加安全、可控&#xff0c;非常适合个人或团队内部使用。 支持操…...

spdlog日志格式化 标志全指南

一、spdlog格式化核心机制 SPDLOG通过set_pattern()函数实现灵活的日志格式定制&#xff0c;该函数解析用户提供的格式字符串&#xff0c;生成包含时间、源代码、进程等信息的结构化日志。其底层由pattern_formatter类处理&#xff0c;通过识别%标志符的组合动态生成格式化器对…...

http接口性能优化方案

设计高响应时间的HTTP查询接口&#xff08;<80ms&#xff09; 要实现跨机房调用的HTTP接口并保持响应时间在80ms以下&#xff0c;确实面临多个技术挑战。以下是关键点和解决方案&#xff1a; 主要技术难点 网络延迟&#xff1a;跨机房物理距离导致的传输延迟 TCP握手/挥手…...

Express知识框架

一、核心概念 1. Express 简介 Node.js 的 Web 框架&#xff0c;提供 HTTP 服务器封装 轻量级但灵活&#xff0c;支持中间件扩展 基于路由&#xff0c;支持 RESTful API 和传统 MVC 架构 无内置 ORM 或模板引擎&#xff0c;但可集成第三方库 2. 核心对象 express() - 创建…...

调出事件查看器界面的4种方法

方法1. 方法2. 方法3. 方法4....

Bash 执行命令的基本流程

是的&#xff0c;Bash 在执行外部命令&#xff08;如 ls、grep 等非内置命令&#xff09;时&#xff0c;确实会调用 exec 系列函数来实现进程程序替换。以下是其底层机制的分步解析&#xff1a; 1. Bash 执行命令的基本流程 当在 Bash 中键入一个命令&#xff08;例如 ls -l&a…...

我们来学mysql -- 安装8.4版本

8.4版本 下载解压用户目录&用户权限my.cnf初始化普通启动safe启动检查启动用户登录远程登录用户root% 下载 地址选择安装包 查看OS位数 getconf LONG_BIT 二进制安装包说明 二进制包的文件名会包含 linux 或 glibc 等字样如&#xff1a;mysql-8.4.4-linux-glibc2.28-x86_…...

Java MVC架构在当今时代的技术解析

一、前言 MVC&#xff08;Model-View-Controller&#xff09;架构作为经典的设计模式&#xff0c;经历了数十年的演进。尽管新兴技术层出不穷&#xff0c;Java MVC仍然在企业级开发中占据重要地位。 二、Java MVC核心优势 1. 模块化分层设计 职责分离&#xff1a;数据层&…...

FPGA----基于ZYNQ 7020实现定制化的EPICS程序开发

引言:基于前文,我们在FPGA侧实现了一些外设驱动功能,并将其导出为hdf生成了他的petalinux,借助ALINX的Debian8做了我们自己的根文件系统。现在,我们需要在petalinux下开发一个epics程序,可以调用我们FPGA的驱动。 1、整体程序架构 注意:我们基于ALINX的根文件系统是不完…...

配置hosts

打开文件 右键点击「记事本」或其他文本编辑器&#xff0c;选择「以管理员身份运行」。 打开路径&#xff1a;C:\Windows\System32\drivers\etc\hosts 添加映射 在文件末尾添加一行&#xff0c;格式为&#xff1a; plaintext IP地址 域名例如&#xff1a; plaintext 127.0.…...

Blender 入门教程(一):模型创建

一、前言 大家都知道&#xff0c;现在 AIGC 领域日新月异&#xff0c;今天 AI 大模型能生成粗糙的 3D 模型&#xff0c;明天就能做成商业级的 3D 模型&#xff0c;但是如果想要一些细节上也有的&#xff0c;还是需要自己手动对模型的布线进行调整&#xff0c;然后再蒙皮等等。…...

JVM对象头中的锁信息机制详解

JVM对象头中的锁信息机制详解 Java中的对象锁机制是高性能并发的基石&#xff0c;而这一切的底层实现都离不开对象头中的 Mark Word 字段。本文将系统梳理JVM对象头中锁信息的存储与演化机制&#xff0c;解析锁升级与批量重偏向优化原理&#xff0c;并通过JOL工具实战验证对象…...

需要低调使用的网盘小工具

周末总是过得飞快&#xff0c;转眼间周一又要来临。今天就不多说&#xff0c;直接给大家分享一款实用的小软件&#xff01; 软件介绍 今天给大家带来一款网盘转存工具——BaiduPanFilesTransfers。 这款工具是某知名网盘的批量转存利器。软件作者已经编写了一份非常详细的说明…...

js fetch流式请求 AI动态生成文本,实现逐字生成渲染效果

开启流式请求&#xff1a;向后端接口发起普通的 fetch&#xff0c;它会返回一个包含 ReadableStream 的 Response 对象获取流式读取器&#xff1a;调用 response.body.getReader() 获取一个 ReadableStreamDefaultReader 实例循环读取数据块&#xff1a;在 while(true) 循环或 …...

软考教材重点内容 信息安全工程师 第24章 工控安全需求分析与安全保护工程

24.1.1 工业控制系统概念及组成 工业控制系统是由各种控制组件、监测组件、数据处理与展示组件共同构成的对工业生产过程进行控制和监控的业务流程管控系统。工业控制系统通常简称工控系统(ICS)。工控系统通常分为离散制造类和过程控制类两大类&#xff0c;控制系统包括 SCADA…...

BGP基础实验

一、配置思路 AS200 内部路由由 OSPF 负责&#xff0c;使 AR2 ~ AR5 内部环回地址可达。 各 AS 之间通过 BGP 实现跨域路由互通。 通过 import-route ospf 语句将 OSPF 路由导入 BGP&#xff0c;再由 BGP 向外通告。 使用 network 命令通告本地环回地址。 AR1 <Huawei…...

遭遇DDoS攻击为什么不能反击回去?

遭遇DDoS&#xff08;分布式拒绝服务&#xff09;攻击时&#xff0c;不能直接“反击回去”的原因涉及技术、法律、道德和实际操作的多个层面。以下是详细分析&#xff1a; 1. 技术难题&#xff1a;难以定位真正的攻击者 分布式攻击源&#xff1a;DDoS攻击的特点是流量来自全球各…...

专题二:二叉树的深度搜索(二叉树剪枝)

以leetcode814题为例 题目分析&#xff1a; 也就是当你的子树全为0的时候就可以剪掉 算法原理分析&#xff1a; 首先分析问题&#xff0c;你子树全为0的时候才可以干掉&#xff0c;我们可以设递归到某一层的时候如何处理 然后抽象出三个核心问题 也就是假设我们递归到第2层…...

服务器共享文件夹如何实现外网访问

一、远程访问共享文件需求 有些企业在内网服务器上存储了很多重要工作文件&#xff0c;这些文件共享后需要在外网被员工访问 快解析帮助用户远程访问企业内网服务器共享文件夹 二、快解析实现远程共享文件夹的方法 下面简单介绍一下通过内网穿透快解析实现自己服务器上的共享…...

git|gitee仓库同步到github

参考&#xff1a;一次提交更新两个仓库&#xff0c;Get 更优雅的 GitHub/Gitee 仓库镜像同步 文章目录 进入需要使用镜像功能的仓库&#xff0c;进入「管理」找到「仓库镜像管理」选项&#xff0c;点击「添加镜像」按钮绑定github绑定成功后再次点击添加镜像如何申请 GitHub 私…...

1.Redis-key的基本命令

&#xff08;一&#xff09;Redis的基本类型 String&#xff0c;List&#xff0c;Set&#xff0c;Hash&#xff0c;Zset 三种特殊类型&#xff1a;geospatial&#xff08;地理空间数据&#xff09;、hyperloglog[基数估算&#xff08;去重计数&#xff09;]、bitmaps(位图&…...

配置Hadoop集群环境-使用脚本命令实现集群文件同步

在 Hadoop 集群环境中&#xff0c;确保各节点配置文件一致至关重要。以下是使用 rsync 结合 SSH 实现集群文件同步的脚本方案&#xff0c;支持批量同步文件到所有节点&#xff1a; 1. 前提条件 所有节点已配置 SSH 免密登录主节点&#xff08;NameNode&#xff09;能通过主机…...

搭建高可用及负载均衡的Redis

搭建高可用及负载均衡的Redis系统是确保数据存储和访问高效且可靠的关键。本文将详细介绍如何配置高可用的Redis集群&#xff0c;并通过负载均衡实现性能优化。 高可用Redis架构设计 高可用性是指系统在部分组件失效时仍能继续运行。对于Redis&#xff0c;高可用架构通常包括…...

Hepatology | 南京鼓楼医院余德才团队:从「无药可用」到「精准打击」!肝癌脂肪代谢分型让3类患者各有生路!

文章标题&#xff1a;Multiomics identifies metabolic subtypes based on fatty acid degradation allocating personalized treatment in hepatocellular carcinoma 发表期刊&#xff1a;Hepatology 影响因子&#xff1a;12.9 客户单位&#xff1a;南京市鼓楼医院 百趣提…...

【日撸 Java 三百行】Day 11(顺序表(一))

目录 Day 11&#xff1a;顺序表&#xff08;一&#xff09; 一、关于顺序表 二、关于面向对象 三、代码模块分析 1. 顺序表的属性 2. 顺序表的方法 四、代码及测试 拓展&#xff1a; 小结 Day 11&#xff1a;顺序表&#xff08;一&#xff09; Task&#xff1a; 在《数…...

配置集群-日志聚集操作

日志聚集是指将分布式集群中各个节点上的应用程序日志收集并汇总到一个集中的位置&#xff0c;方便后续的查看、分析和管理。在 Hadoop 和 Spark 集群中&#xff0c;日志聚集是一项重要的功能&#xff0c;下面分别介绍如何在这两个集群中配置日志聚集操作。 Hadoop 集群日志聚…...

node版本.node版本、npm版本和pnpm版本对应

报错&#xff1a; ERR_PNPM_META_FETCH_FAIL GET https://registry.npmmirror.com/rollup: Value of "this" must be of type URLSearchParams node版本 Node.js — Node.js Releases node和pnpm对应关系 Installation | pnpm 参考 NVM管理node版本.node版本、…...

电商物流管理优化:从网络重构到成本管控的全链路解析

大家好&#xff0c;我是沛哥儿。作为电商行业&#xff0c;我始终认为物流是电商体验的“最后一公里”&#xff0c;更是成本控制的核心战场。随着行业竞争加剧&#xff0c;如何通过物流网络优化实现降本增效&#xff0c;已成为电商企业的必修课。本文将从物流网络的各个环节切入…...

Java学习手册:客户端负载均衡

一、客户端负载均衡的概念 客户端负载均衡是指在客户端应用程序中&#xff0c;根据一定的算法和策略&#xff0c;将请求分发到多个服务实例上。与服务端负载均衡不同&#xff0c;客户端负载均衡不需要通过专门的负载均衡设备或服务&#xff0c;而是直接在客户端进行请求的分发…...

E+H流量计与Profibus DP主站转Modbus RTU/TCP网关通讯

EH流量计与Profibus DP主站转Modbus RTU/TCP网关通讯 随着工业自动化的不断发展&#xff0c;各种不同品牌、型号的设备需要进行数据交互和通信。在实际应用中&#xff0c;EH流量计作为一种常用的流量测量设备&#xff0c;常常需要与其他设备进行连接和通信。而Profibus DP是一…...

mysql配置输入错误密码3次后锁定60s

mysql配置输入错误密码3次后锁定60s 1、安装插件 INSTALL PLUGIN CONNECTION_CONTROL SONAME connection_control.so; INSTALL PLUGIN CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS SONAME connection_control.so; 2、验证是否安装成功 SHOW VARIABLES LIKE connection_control…...

首屏优化,webpack插件用于给html中js自动添加异步加载属性

因为要使用cheerio库&#xff0c;需要安装 npm安装 npm install cheerio --save-dev或使用 yarn安装 yarn add cheerio --dev创建async-script-webpack-plugin.js const cheerio require(cheerio);class AsyncScriptWebpackPlugin {constructor(options {}) {this.options …...

SQLite 数据库常见问题及解决方法

一、数据库文件锁定问题 1. 问题表现 在多线程或多进程环境下访问 SQLite 数据库时&#xff0c;常常会出现数据库文件被锁定的情况。当一个进程对数据库执行写操作时&#xff0c;其他进程的读写操作都会被阻塞&#xff0c;导致应用程序出现卡顿甚至无响应。比如在移动应用开发…...

day 23

机器学习管道 pipeline 一般通用pipeline的实现流程&#xff1a; 1.构建多个转换器&#xff08;transformer&#xff09;&#xff0c;来实现对特征的预处理 2.构建 ColumnTransformer&#xff0c;将不同的预处理应用于不同的列子集&#xff0c;构造一个完备的转化器 3.构建…...

MATLAB复制Excel数据到指定区域

Matlab中如何将Excel表中的265-528行F-AA列数据复制到1-263行AE-AZ中 版本&#xff1a;MatlabR2018b clc; clear; %旧Excel文件名 oldFile ; %新Excel文件名 newFile ; % 工作表名称&#xff08;旧表和新表一致&#xff09; sheetName Sheet1; % 旧文件中待复制的数据范…...