FFmpeg初步了解

news2024/10/1 19:57:55

一、了解FFmpeg

1.1 什么是FFmpeg

        FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

      FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。这个项目最早由Fabrice Bellard发起,2004年至2015年间由Michael Niedermayer主要负责维护。许多FFmpeg的开发人员都来自MPlayer项目,而且当前FFmpeg也是放在MPlayer项目组的服务器上。项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。FFmpeg编码库可以使用GPU加速。

1.2 FFmpeg的功能

1.2.1 视频采集功能

        FFmpeg视频采集功能非常强大,不仅可以采集视频采集卡或USB摄像头的图像,还可以进行屏幕录制,同时还支持以RTP方式将视频流传送给支持RTSP的流媒体服务器,支持直播应用。

1.2.2 视频格式转换功能

        FFmpeg视频转换功能。视频格式转换,比如可以将多种视频格式转换为flv格式,可不是视频信号转换 。

        FFmpeg可以轻易地实现多种视频格式之间的相互转换(wma,rm,avi,mod等),例如可以将摄录下的视频avi等转成视频网站所采用的flv格式。

1.2.3 视频截图功能

        对于选定的视频,截取指定时间的缩略图。视频抓图,获取静态图和动态图,不提倡抓gif文件;因为抓出的gif文件大而播放不流畅。

1.2.4 给视频加水印功能

        使用FFmpeg视频添加水印(watermark)。

1.3 项目组成

        FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它包括了领先的音/视频编码库libavcodec等。

libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;

libavcodec:用于各种类型声音/图像编解码;

libavutil:包含一些公共的工具函数;

libswscale:用于视频场景比例缩放、色彩映射转换;

libpostproc:用于后期效果处理;

ffmpeg:该项目提供的一个工具,可用于格式转换、解码或电视卡即时编码等;

ffsever:一个 HTTP 多媒体即时广播串流服务器;

ffplay:是一个简单的播放器,使用ffmpeg 库解析和解码,通过SDL显示;

1.4 FFmpeg下载

Download FFmpegDownload FFmpegDownload FFmpeg

根据需求选择版本,免费开源。

二、学习笔记

2.1 视频文件解码流程

2.2 封装与编码格式

2.3 ffmpeg 视频解码 (解码为YUV)

2.3.1 方案流程图

注释:

1、av_register_all():注册所有组件。

2、avformat_open_input():打开输入视频文件。

3、avformat_find_stream_info():获取视频文件信息

4、avcodec_find_decoder():查找解码器。

5、avcodec_open2():打开解码器。

6、av_read_frame():从输入文件读取一帧压缩数据。

7、avcodec_decode_video2():解码一帧压缩数据。

8、avcodec_close():关闭解码器。

9、avformat_close_input():关闭输入视频文件。

2.3.2 视频解码程序

/**

​

* FFMPEG视频解码流程

* 1、av_register_all():注册所有组件。

* 2、avformat_open_input():打开输入视频文件。

* 3、avformat_find_stream_info():获取视频文件信息

* 4、avcodec_find_decoder():查找解码器。

* 5、avcodec_open2():打开解码器。

* 6、av_read_frame():从输入文件读取一帧压缩数据。

* 7、avcodec_decode_video2():解码一帧压缩数据。

* 8、avcodec_close():关闭解码器。

* 9、avformat_close_input():关闭输入视频文件。

*/

​

#include "stdafx.h"

#include <stdio.h>

​

#define __STDC_CONSTANT_MACROS

​

#ifdef _WIN32

​

//Windows

extern "C"

{

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

#include "libswscale/swscale.h"

#include "libavutil/imgutils.h"

};

#else

​

//Linux...

#ifdef __cplusplus

extern "C"

{

#endif

​

#include <libavcodec/avcodec.h>

#include <libavformat/avformat.h>

#include <libswscale/swscale.h>

#include <libavutil/imgutils.h>

#ifdef __cplusplus

};

#endif

#endif

​

int main()

{

    //文件格式上下文

    AVFormatContext *pFormatCtx;    // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。

    int     i = 0, videoindex;

    AVCodecContext  *pCodecCtx;     // 编码器上下文结构体,保存了视频(音频)编解码相关信息。

    AVCodec         *pCodec;        // AVCodec是存储编解码器信息的结构体。

    AVFrame *pFrame, *pFrameYUV;    // AVFrame是包含码流参数较多的结构体

    unsigned char *out_buffer;

    AVPacket *packet;               // AVPacket是存储压缩编码数据相关信息的结构体

int y_size;

int ret, got_picture;

​

// struct SwsContext结构体位于libswscale类库中, 该类库主要用于处理图片像素数据, 可以完成图片像素格式的转换, 图片的拉伸等工作.

struct SwsContext *img_convert_ctx;

char filepath[] = "input.mkv";

FILE *fp_yuv = fopen("output.yuv", "wb+");

av_register_all();    // 注册所有组件

avformat_network_init();   // 对网络库进行全局初始化。

pFormatCtx = avformat_alloc_context();   // 初始化AVFormatContext结构体指针。使用avformat_free_context()释放内存。

if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)  // 打开输入流并读取header。必须使用avformat_close_input()接口关闭。

{

    printf("Couldn't open input stream.\n");

    return -1;

}

//读取一部分视音频数据并且获得一些相关的信息

if (avformat_find_stream_info(pFormatCtx, NULL) < 0) // 读取媒体文件的包以获取流信息

{

    printf("Couldn't find stream information.\n");

    return -1;

}

​

//查找视频编码索引

videoindex = -1;

for (i = 0; i < pFormatCtx->nb_streams; i++)

{

    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)

    {

        videoindex = i;

        break;

    }

}

​

if (videoindex == -1)

{

    printf("Didn't find a video stream.\n");

    return -1;

}

​

//编解码上下文

pCodecCtx = pFormatCtx->streams[videoindex]->codec;

//查找解码器

pCodec = avcodec_find_decoder(pCodecCtx->codec_id); // 查找符合ID的已注册解码器

if (pCodec == NULL)

{

    printf("Codec not found.\n");

    return -1;

}

//打开解码器

if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)

{

    printf("Could not open codec.\n");

    return -1;

}

​

//申请AVFrame,用于原始视频

pFrame = av_frame_alloc();

//申请AVFrame,用于yuv视频

pFrameYUV = av_frame_alloc();

//分配内存,用于图像格式转换

out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));

// 根据指定的图像参数和提供的数组设置参数指针和linesize大小

av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

packet = (AVPacket *)av_malloc(sizeof(AVPacket));

//Output Info-----------------------------

printf("--------------- File Information ----------------\n");

//手工调试函数,输出tbn、tbc、tbr、PAR、DAR的含义

av_dump_format(pFormatCtx, 0, filepath, 0);

printf("-------------------------------------------------\n");

​

//申请转换上下文。 sws_getContext功能:初始化 SwsContext 结构体指针  

img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,

pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

​

//读取数据

while (av_read_frame(pFormatCtx, packet) >= 0) // 读取码流中的音频若干帧或者视频一帧

{

    if (packet->stream_index == videoindex)

    {

        // avcodec_decode_video2 功能:解码一帧视频数据

        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

        if (ret < 0)

        {

            printf("Decode Error.\n");

            return -1;

        }

​

        if (got_picture >= 1)

        {

            //成功解码一帧

            sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,

                pFrameYUV->data, pFrameYUV->linesize); // 转换图像格式

​

            y_size = pCodecCtx->width*pCodecCtx->height;

            // fwrite 功能:把 pFrameYUV 所指向数据写入到 fp_yuv 中。

            fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);    //Y

            fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);  //U

            fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);  //V

            printf("Succeed to decode 1 frame!\n");

        }

        else

        {

            //未解码到一帧,可能时结尾B帧或延迟帧,在后面做flush decoder处理

        }

    }

    av_free_packet(packet); // free

}

​

//flush decoder

//FIX: Flush Frames remained in Codec

while (true)

{

    if (!(pCodec->capabilities & CODEC_CAP_DELAY))

        return 0;

    // avcodec_decode_video2 功能:解码一帧视频数据

    ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

    if (ret < 0)

    {

        break;

    }

    if (!got_picture)

    {

        break;

    }

​

    sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,

        pFrameYUV->data, pFrameYUV->linesize); // 转换图像格式

​

    int y_size = pCodecCtx->width*pCodecCtx->height;

    // fwrite 功能:把 pFrameYUV 所指向数据写入到 fp_yuv 中。

    fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);    //Y

    fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);  //U

    fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);  //V

    printf("Flush Decoder: Succeed to decode 1 frame!\n");

}

​

sws_freeContext(img_convert_ctx);

av_frame_free(&pFrameYUV);

av_frame_free(&pFrame);

avcodec_close(pCodecCtx);

avformat_close_input(&pFormatCtx);

fclose(fp_yuv);

​

return 0;

}

2.3.3 注意事项

解码后数据需要经过处理,去除无效数据,如下:

用sws_scale() 进行转换。

2.4 ffmpeg 视频编码 (YUV编码为H.264)

2.4.1 方案流程图

注释:

1、av_register_all():注册FFmpeg所有编解码器。

2、avformat_alloc_output_context2():初始化输出码流的AVFormatContext。

3、avio_open():打开输出文件。

4、av_new_stream():创建输出码流的AVStream。

5、avcodec_find_encoder():查找编码器。

6、avcodec_open2():打开编码器。

7、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

8、不停地从码流中提取出YUV数据,进行编码。 avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。 av_write_frame():将编码后的视频码流写入文件。

9、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。

10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

2.4.2 视频编码程序

/**

*************** FFMPEG视频编码流程 *******************

​

* 01、av_register_all():注册FFmpeg所有编解码器;

* 02、avformat_alloc_output_context2():初始化输出码流的AVFormatContext;

* 03、avio_open():打开输出文件;

* 04、av_new_stream():创建输出码流的AVStream;

* 05、avcodec_find_encoder():查找编码器;

* 06、avcodec_open2():打开编码器;

* 07、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS);

* 08、不停地从码流中提取出YUV数据,进行编码;

* avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据);

* av_write_frame():将编码后的视频码流写入文件;

* 09、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket;

* 10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS);

*/

​

#include <stdio.h>

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32

//Windows

extern "C"

{

#include "libavutil/opt.h"

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

};

#else

//Linux...

#ifdef __cplusplus

extern "C"

{

#endif

#include <libavutil/opt.h>

#include <libavcodec/avcodec.h>

#include <libavformat/avformat.h>

#ifdef __cplusplus

};

#endif

#endif

​

// 输入的像素数据读取完成后调用此函数,用于输出编码器中剩余的AVPacket

int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){

    int ret;

    int got_frame;

    AVPacket enc_pkt;

    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities & CODEC_CAP_DELAY))

        return 0;

    while (1) {

        enc_pkt.data = NULL;

        enc_pkt.size = 0;

        av_init_packet(&enc_pkt);

        //编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。

        ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,

            NULL, &got_frame);

        av_frame_free(NULL);

        if (ret < 0)

            break;

        if (!got_frame){

            ret=0;

            break;

        }

        printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);

        /* mux encoded frame */

        ret = av_write_frame(fmt_ctx, &enc_pkt);

        if (ret < 0)

            break;

    }

    return ret;

}

​

int main(int argc, char* argv[])

{

    AVFormatContext* pFormatCtx; // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。  

    AVOutputFormat* fmt;         // AVOutputFormat 结构体主要用于muxer,是音视频文件的一个封装器。

    AVStream* video_st;          // AVStream是存储每一个视频/音频流信息的结构体。

    AVCodecContext* pCodecCtx;   // 编码器上下文结构体,保存了视频(音频)编解码相关信息。

    AVCodec* pCodec;             // AVCodec是存储编解码器信息的结构体。

    AVPacket pkt;                // AVPacket是存储压缩编码数据相关信息的结构体

    uint8_t* picture_buf;

    AVFrame* pFrame;             // AVFrame是包含码流参数较多的结构体

    int picture_size;

    int y_size;

    int framecnt=0;

    //FILE *in_file = fopen("src01_480x272.yuv", "rb"); // 输入原始YUV数据

    FILE *in_file = fopen("../ds_480x272.yuv", "rb");   // 输入原始YUV数据

    int in_w=480,in_h=272;                              // 输入数据的宽度和高度

    int framenum=100;                                   // 要编码的帧

    //const char* out_file = "src01.h264";              // 输出文件路径

    //const char* out_file = "src01.ts";

    //const char* out_file = "src01.hevc";

    const char* out_file = "ds.h264";

av_register_all(); // 注册ffmpeg所有编解码器

//方法1.

pFormatCtx = avformat_alloc_context(); // 初始化 pFormatCtx。 AVFormatContext 用 avformat_alloc_context() 进行初始化

//Guess Format

fmt = av_guess_format(NULL, out_file, NULL); // av_guess_format 这是一个决定视频输出时封装方式的函数,其中有三个参数,写任何一个参数,都会自动匹配相应的封装方式。

pFormatCtx->oformat = fmt;

​

//方法2.

//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file); // 初始化输出码流的AVFormatContext

//fmt = pFormatCtx->oformat;

//Open output URL

if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){ // avio_open 打开输出文件

    printf("Failed to open output file! \n");

    return -1;

}

​

video_st = avformat_new_stream(pFormatCtx, 0); // 创建输出码流的AVStream

video_st->time_base.num = 1;  // num 分子

video_st->time_base.den = 25; // den 分母

​

if (video_st==NULL){

    return -1;

}

// 必须设置的参数

pCodecCtx = video_st->codec;

//pCodecCtx->codec_id =AV_CODEC_ID_HEVC;

pCodecCtx->codec_id = fmt->video_codec;

pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

pCodecCtx->pix_fmt = PIX_FMT_YUV420P;

pCodecCtx->width = in_w;  

pCodecCtx->height = in_h;

pCodecCtx->time_base.num = 1;  

pCodecCtx->time_base.den = 25;  

pCodecCtx->bit_rate = 400000;  

pCodecCtx->gop_size=250;

//H264

//pCodecCtx->me_range = 16;

//pCodecCtx->max_qdiff = 4;

//pCodecCtx->qcompress = 0.6;

pCodecCtx->qmin = 10;

pCodecCtx->qmax = 51;

​

// 可选参数

pCodecCtx->max_b_frames=3;

​

// 设置选项

AVDictionary *param = 0;

//H.264

if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {

    av_dict_set(¶m, "preset", "slow", 0);

    av_dict_set(¶m, "tune", "zerolatency", 0);

    //av_dict_set(¶m, "profile", "main", 0);

}

//H.265

if(pCodecCtx->codec_id == AV_CODEC_ID_H265){

    av_dict_set(¶m, "preset", "ultrafast", 0);

    av_dict_set(¶m, "tune", "zero-latency", 0);

}

​

//Show some Information

av_dump_format(pFormatCtx, 0, out_file, 1); // av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。

​

pCodec = avcodec_find_encoder(pCodecCtx->codec_id); // 查找编码器

if (!pCodec){

    printf("Can not find encoder! \n");

    return -1;

}

if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){ // 打开编码器

    printf("Failed to open encoder! \n");

    return -1;

}

pFrame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存

picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); //计算这个格式的图片,需要多少字节来存储  

picture_buf = (uint8_t *)av_malloc(picture_size);

​

// 这个函数是为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间

avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

​

// 写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

avformat_write_header(pFormatCtx,NULL);

​

av_new_packet(&pkt,picture_size); // 分配数据包的有效size并初始化

​

y_size = pCodecCtx->width * pCodecCtx->height;

​

// 一帧一帧循环操作

for (int i=0; i<framenum; i++){

    // Read raw YUV data

    if (fread(picture_buf, 1, y_size*3/2, in_file) <= 0){ // fread函数,从文件流中读取数据,如果不成功或读到文件末尾返回 0

        printf("Failed to read raw data! \n");

        return -1;

    }else if(feof(in_file)){ // 判断文件是否结束

        break;

    }

    pFrame->data[0] = picture_buf;              // Y

    pFrame->data[1] = picture_buf+ y_size;      // U

    pFrame->data[2] = picture_buf+ y_size*5/4;  // V

    // PTS

    pFrame->pts=i; // pts : 以时间为基本单位的表示时间戳(应该向用户显示帧的时间)。

    int got_picture=0;

​

    // 编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。

    // 成功时返回0,失败时返回负错误代码 失败时返回错误返回码

    int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);

    if(ret < 0){

        printf("Failed to encode! \n");

        return -1;

    }

    if (got_picture==1){

        printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);

        framecnt++;

        pkt.stream_index = video_st->index;

        ret = av_write_frame(pFormatCtx, &pkt); // 将编码后的视频码流写入文件,

        av_free_packet(&pkt); // free

    }

}

// Flush Encoder

int ret = flush_encoder(pFormatCtx,0); // 输入的像素数据读取完成后调用此函数,用于输出编码器中剩余的AVPacket

if (ret < 0) {

    printf("Flushing encoder failed\n");

    return -1;

}

​

// 写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)

av_write_trailer(pFormatCtx);

​

// Clean

if (video_st){

    avcodec_close(video_st->codec);

    av_free(pFrame);

    av_free(picture_buf);

}

avio_close(pFormatCtx->pb);

avformat_free_context(pFormatCtx);

​

fclose(in_file);

​

return 0;

}

2.5 ffmpeg 音频编码 (PCM编码为AAC)

2.5.1 方案流程图

注释:

1、av_register_all():注册FFmpeg所有编解码器。

2、avformat_alloc_output_context2():初始化输出码流的AVFormatContext。

3、avio_open():打开输出文件。

4、av_new_stream():创建输出码流的AVStream。

5、avcodec_find_encoder():查找编码器。

6、avcodec_open2():打开编码器。

7、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比 如说MPEG2TS)。

8、avcodec_encode_audio2():编码音频。即将AVFrame(存储PCM采样数据)编码为AVPacket(存储AAC,MP3等格式的码流数据)。

9、av_write_frame():将编码后的视频码流写入文件。

10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

2.5.2 音频编码程序

#include <stdio.h>

#include "audio_encoder.h"

#define __STDC_CONSTANT_MACROS

​

#ifdef _WIN32

//Windows

extern "C"

{

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

};

#else

//Linux...

#ifdef __cplusplus

extern "C"

{

#endif

#include <libavcodec/avcodec.h>

#include <libavformat/avformat.h>

#ifdef __cplusplus

};

#endif

#endif

​

​

int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){

    int ret;

    int got_frame;

    AVPacket enc_pkt;

    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &

        CODEC_CAP_DELAY))

        return 0;

    while (1) {

        enc_pkt.data = NULL;

        enc_pkt.size = 0;

        av_init_packet(&enc_pkt);

        ret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,

            NULL, &got_frame);

        av_frame_free(NULL);

        if (ret < 0)

            break;

        if (!got_frame){

            ret=0;

            break;

        }

        printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);

        /* mux encoded frame */

        ret = av_write_frame(fmt_ctx, &enc_pkt);

        if (ret < 0)

            break;

    }

    return ret;

}

​

int main(int argc, char* argv[])

{

    AudioEncoder audioEncoder("tdjm.pcm","tdjm.aac");

    audioEncoder.encode_now();

    AVFormatContext* pFormatCtx;   // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。  

    AVOutputFormat* fmt;           // AVOutputFormat 结构体主要用于muxer,是音视频文件的一个封装器。

    AVStream* audio_st;            // AVStream是存储每一个视频/音频流信息的结构体。

    AVCodecContext* pCodecCtx;     // 编码器上下文结构体,保存了视频(音频)编解码相关信息。

    AVCodec* pCodec;               // AVCodec是存储编解码器信息的结构体。

uint8_t* frame_buf;

AVFrame* pFrame;               // AVFrame是包含码流参数较多的结构体

AVPacket pkt;                  // AVPacket是存储压缩编码数据相关信息的结构体

​

int got_frame=0;

int ret=0;

int size=0;

​

FILE *in_file=NULL;                         //Raw PCM data

int framenum=1000;                          //Audio frame number

const char* out_file = "tdjm.aac";          //Output URL

int i;

​

in_file= fopen("tdjm.pcm", "rb");

​

av_register_all();  // 注册ffmpeg所有编解码器

​

//Method 1.

pFormatCtx = avformat_alloc_context();   // 初始化 pFormatCtx。 AVFormatContext 用 avformat_alloc_context() 进行初始化

fmt = av_guess_format(NULL, out_file, NULL); // av_guess_format 这是一个决定视频输出时封装方式的函数,其中有三个参数,写任何一个参数,都会自动匹配相应的封装方式。

pFormatCtx->oformat = fmt;

//Method 2.

//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);

//fmt = pFormatCtx->oformat;

​

//Open output URL

if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){ // avio_open 打开输出文件

    printf("Failed to open output file!\n");

    return -1;

}

​

audio_st = avformat_new_stream(pFormatCtx, 0); // 创建输出码流的AVStream

if (audio_st==NULL){

    return -1;

}

// 必须设置的参数

pCodecCtx = audio_st->codec;

pCodecCtx->codec_id = fmt->audio_codec;

pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;

pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;

pCodecCtx->sample_rate= 44100;

pCodecCtx->channel_layout=AV_CH_LAYOUT_STEREO;

pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);

pCodecCtx->bit_rate = 64000;  

​

// av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。

av_dump_format(pFormatCtx, 0, out_file, 1);

​

// 查找编码器

pCodec = avcodec_find_encoder(pCodecCtx->codec_id);

if (!pCodec){

    printf("Can not find encoder!\n");

    return -1;

}

// 打开编码器

if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0){

    printf("Failed to open encoder!\n");

    return -1;

}

pFrame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存

pFrame->nb_samples= pCodecCtx->frame_size; // 此帧描述的音频采样数(每个通道)

pFrame->format= pCodecCtx->sample_fmt; // 帧的格式,如果未知或未设置,则为-1

​

// 获取给定音频参数所需的缓冲区大小。

size = av_samples_get_buffer_size(NULL, pCodecCtx->channels,pCodecCtx->frame_size,pCodecCtx->sample_fmt, 1);

frame_buf = (uint8_t *)av_malloc(size);

// 填充AVFrame音频数据和linesize指针。

avcodec_fill_audio_frame(pFrame, pCodecCtx->channels, pCodecCtx->sample_fmt,(const uint8_t*)frame_buf, size, 1);

​

// 写文件头

avformat_write_header(pFormatCtx,NULL);

​

av_new_packet(&pkt,size); // 分配有效size并初始化

​

for (i=0; i<framenum; i++){

    //读取 PCM 数据

    if (fread(frame_buf, 1, size, in_file) <= 0){

        printf("Failed to read raw data! \n");

        return -1;

    }else if(feof(in_file)){

        break;

    }

    pFrame->data[0] = frame_buf;  //PCM Data

​

    pFrame->pts=i*100;

    got_frame=0;

    // 编码音频。即将AVFrame(存储PCM采样数据)编码为AVPacket(存储AAC,MP3等格式的码流数据)。

    ret = avcodec_encode_audio2(pCodecCtx, &pkt,pFrame, &got_frame);

    if(ret < 0){

        printf("Failed to encode!\n");

        return -1;

    }

    if (got_frame==1){

        printf("Succeed to encode 1 frame! \tsize:%5d\n",pkt.size);

        pkt.stream_index = audio_st->index;

        ret = av_write_frame(pFormatCtx, &pkt); // 将编码后的音频数据写入文件

        av_free_packet(&pkt);

    }

}

​

// 用于输出编码器中剩余的AVPacket

ret = flush_encoder(pFormatCtx,0);

if (ret < 0) {

    printf("Flushing encoder failed\n");

    return -1;

}

​

// 写文件尾

av_write_trailer(pFormatCtx);

​

// Clean

if (audio_st){

    avcodec_close(audio_st->codec);

    av_free(pFrame);

    av_free(frame_buf);

}

avio_close(pFormatCtx->pb);

avformat_free_context(pFormatCtx);

​

fclose(in_file);

​

return 0;

}

2.6 ffmpeg 音频解码

2.6.1 方案一 (存为pcm)

#ifdef  __cplusplus

extern "C"

{

#endif

​

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

#include "libavdevice/avdevice.h"

#include "libavfilter/avfilter.h"

#include "libavfilter/avfiltergraph.h"

#include "libavfilter/buffersink.h"

#include "libavfilter/buffersrc.h"

#include "libavutil/audio_fifo.h"

#include "libavutil/avutil.h"

#include "libavutil/fifo.h"

​

#ifdef  __cplusplus

}

#endif

​

#pragma comment(lib, "avcodec.lib")

#pragma comment(lib, "avformat.lib")

#pragma comment(lib, "avutil.lib")

#pragma comment(lib, "avdevice.lib")

#pragma comment(lib, "avfilter.lib")

​

//#pragma comment(lib, "avfilter.lib")

//#pragma comment(lib, "postproc.lib")

//#pragma comment(lib, "swresample.lib")

#pragma comment(lib, "swscale.lib")

​

#include <windows.h>

#include <conio.h>

#include <time.h>

#include <tchar.h>

​

AVFormatContext *ifmt_ctx = NULL;  

int g_AudioStreamIndex = -1;

​

#include <stdio.h>  

​

int openinputfile(const char* filename)  

{  

    int ret = 0;  

    //open the input  

    if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0)  

    {  

        printf("can not open input");  

        return ret;  

    }  

    if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)))  

    {  

        printf("can not find input stream info");  

        return ret;  

    }  

//open the decoder  

for (int i = 0; i < ifmt_ctx->nb_streams; i++)  

{  

    if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)  

    {  

        g_AudioStreamIndex = i;

        ret = avcodec_open2(ifmt_ctx->streams[i]->codec,   

            avcodec_find_decoder(ifmt_ctx->streams[i]->codec->codec_id), NULL);  

​

        if (ret < 0)  

        {  

            printf("can not open decoder");  

            return ret;  

        }  

​

    }  

}  

​

return 0;  

}  

​

int _tmain(int argc, _TCHAR* argv[])  

{  

    if (argc < 2)  

    {  

        return -1;  

    }  

    AVPacket pkt_in, pkt_out;  

    AVFrame *frame = NULL;  

    unsigned int stream_index;  

    av_register_all();  

    if (openinputfile(argv[1]) < 0)  

{  

    printf("failed to open input file");  

    goto end;  

}  

​

FILE *p = NULL;  

char tmpName[100];

sprintf_s(tmpName, "%s_%d_%dchannel.pcm", argv[1],

    ifmt_ctx->streams[g_AudioStreamIndex]->codec->sample_rate, ifmt_ctx->streams[g_AudioStreamIndex]->codec->channels);

p = fopen(tmpName, "w+b");

​

int size = av_get_bytes_per_sample(ifmt_ctx->streams[g_AudioStreamIndex]->codec->sample_fmt);

while(1)  

{  

    if (av_read_frame(ifmt_ctx, &pkt_in) < 0)  

    {  

        break;  

    }  

    pkt_out.data = NULL;  

    pkt_out.size = 0;  

    av_init_packet(&pkt_out);  

​

    if (g_AudioStreamIndex == pkt_in.stream_index)

    {

        stream_index = pkt_in.stream_index;  

        frame = av_frame_alloc();  

        int got_frame = -1;  

        int ret = -1;  

​

        ret = avcodec_decode_audio4(ifmt_ctx->streams[stream_index]->codec, frame, &got_frame, &pkt_in);  

​

        if (ret < 0)  

        {  

            av_frame_free(&frame);  

            printf("decoding audio stream failed\n");  

            break;  

        }  

​

        if (got_frame)  

        {

            if (frame->data[0] && frame->data[1])

            {

                for (int i = 0; i < ifmt_ctx->streams[stream_index]->codec->frame_size; i++)

                {

                    fwrite(frame->data[0] + i * size, 1, size, p);

                    fwrite(frame->data[1] + i * size, 1, size, p);

                }

            }

            else if(frame->data[0])

            {

                fwrite(frame->data[0], 1, frame->linesize[0], p);

            }

        }  

    }       

}  

fclose(p);  

end:  

    avformat_close_input(&ifmt_ctx);  

    printf("enter any key to stop\n");

getchar();  

return 0;

}

2.6.2 方案二 (aac解码pcm)

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#define __STDC_CONSTANT_MACROS

extern "C" {

    #include <libavcodec/avcodec.h>

    #include <libavformat/avformat.h>

    #include <libavdevice/avdevice.h>

    #include<libswresample/swresample.h>

}

​

#define MAX_AUDIO_FRAME_SIZE  192000

​

#define SAMPLE_PRT(fmt...)   \

    do {\

        printf("[%s]-%d: ", __FUNCTION__, __LINE__);\

        printf(fmt);\

       }while(0)

​

const char *in_file = "./hefang.aac";

const char *out_file = "./hefang.pcm";

int main()

{

    //注册所有的工具

    av_register_all();

AVFormatContext *fmt_ctx = NULL;

AVCodecContext  *cod_ctx = NULL;

AVCodec         *cod   = NULL;

 

//分配一个avformat

fmt_ctx = avformat_alloc_context();

if (fmt_ctx == NULL)

    printf("alloc fail");

 

//打开文件,解封装

if (avformat_open_input(&fmt_ctx, in_file, NULL, NULL) != 0)

    printf("open fail");

 

//查找文件的相关流信息

if (avformat_find_stream_info(fmt_ctx, NULL) < 0)

    printf("find stream fail");

 

//输出格式信息

av_dump_format(fmt_ctx, 0, in_file, 0);

 

//查找解码信息

int stream_index = -1;

for (int i = 0; i < fmt_ctx->nb_streams; i++)

    if (fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {

        stream_index = i;

        break;

    }

 

if (stream_index == -1)

    printf("find stream fail");

 

//保存解码器

cod_ctx = fmt_ctx->streams[stream_index]->codec;

cod = avcodec_find_decoder(cod_ctx->codec_id);

 

if (cod == NULL)

    printf("find codec fail");

 

if (avcodec_open2(cod_ctx, cod, NULL) < 0)

    printf("can't open codec");

 

FILE *out_fb = NULL;

out_fb = fopen(out_file, "wb");

 

//创建packet,用于存储解码前的数据

AVPacket *packet = (AVPacket *)malloc(sizeof(AVPacket));

av_init_packet(packet);

 

//设置转码后输出相关参数

//采样的布局方式

uint64_t out_channel_layout = AV_CH_LAYOUT_MONO;

//采样个数

int out_nb_samples = 1024;

//采样格式

enum AVSampleFormat  sample_fmt = AV_SAMPLE_FMT_S16;

//采样率

int out_sample_rate = 44100;

//通道数

int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);

printf("%d\n",out_channels);

//创建buffer

int buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, sample_fmt, 1);

//注意要用av_malloc

uint8_t *buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);

//创建Frame,用于存储解码后的数据

AVFrame *frame = av_frame_alloc();

 

int got_picture;

 

int64_t in_channel_layout = av_get_default_channel_layout(cod_ctx->channels);

//打开转码器

struct SwrContext *convert_ctx = swr_alloc();

//设置转码参数

convert_ctx = swr_alloc_set_opts(convert_ctx, out_channel_layout, sample_fmt, out_sample_rate, \

        in_channel_layout, cod_ctx->sample_fmt, cod_ctx->sample_rate, 0, NULL);

//初始化转码器

swr_init(convert_ctx);

 

//while循环,每次读取一帧,并转码

 

while (av_read_frame(fmt_ctx, packet) >= 0) {

 

    if (packet->stream_index == stream_index) {

 

        //解码声音

        if (avcodec_decode_audio4(cod_ctx, frame, &got_picture, packet) < 0) {

            printf("decode error");

            return -1;

        }

 

        if (got_picture > 0) {

            //转码

            swr_convert(convert_ctx, &buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)frame->data, frame->nb_samples);

 

            printf("pts:%10lld\t packet size:%d\n", packet->pts, packet->size);

 

            fwrite(buffer, 1, buffer_size, out_fb);

        }

        got_picture=0;

    }

 

    av_free_packet(packet);

}

 

swr_free(&convert_ctx);

 

fclose(out_fb);

 

return 0;

}

2.7 ffmpeg 转码 (FLV 转码为AVI)

2.7.1 转码理论流程

2.7.2 转码流程图

注释:

1、open_input_file():打开输入文件,并初始化相关的结构体。

2、open_output_file():打开输出文件,并初始化相关的结构体。

3、init_filters():初始化AVFilter相关的结构体。

4、av_read_frame():从输入文件中读取一个AVPacket。

5、avcodec_decode_video2():解码一个视频AVPacket(存储H.264等压缩码流数据)为AVFrame(存储YUV等非压缩的像素数据)。

6、avcodec_decode_video4():解码一个音频AVPacket(存储MP3等压缩码流数据)为AVFrame(存储PCM采样数据)。

7、filter_encode_write_frame():编码一个AVFrame。

8、flush_encoder():输入文件读取完毕后,输出编码器中剩余的AVPacket。

2.7.3 转码代码

#include "stdafx.h"

extern "C"

{

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

#include "libavfilter/avfiltergraph.h"

#include "libavfilter/avcodec.h"

#include "libavfilter/buffersink.h"

#include "libavfilter/buffersrc.h"

#include "libavutil/avutil.h"

#include "libavutil/opt.h"

#include "libavutil/pixdesc.h"

};

​

​

static AVFormatContext *ifmt_ctx;

static AVFormatContext *ofmt_ctx;

typedef struct FilteringContext {

    AVFilterContext *buffersink_ctx;

    AVFilterContext *buffersrc_ctx;

    AVFilterGraph *filter_graph;

} FilteringContext;

static FilteringContext *filter_ctx;

​

// 打开输入文件

static int open_input_file(const char *filename)

{

    int ret;

    unsigned int i;

    ifmt_ctx = NULL;

    // 打开多媒体数据并且获得一些相关的信息

    if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) {

        av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");

        return ret;

    }

    // 获取视频流信息

    if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {

        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");

        return ret;

    }

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {

        AVStream *stream; // AVStream是存储每一个视频/音频流信息的结构体。

        AVCodecContext *codec_ctx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。

        stream = ifmt_ctx->streams[i];

        codec_ctx = stream->codec;

        // 重新编码视频、音频和字幕等

        if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO

                || codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {

            /* Open decoder */

            ret = avcodec_open2(codec_ctx,

                    avcodec_find_decoder(codec_ctx->codec_id), NULL); // avcodec_open2 该函数用于初始化一个视音频编解码器的AVCodecContext

            if (ret < 0) {

                av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i);

                return ret;

            }

        }

    }

    // av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。

    av_dump_format(ifmt_ctx, 0, filename, 0);

    return 0;

}

​

// 打开输出文件

static int open_output_file(const char *filename)

{

    AVStream *out_stream; // AVStream是存储每一个视频/音频流信息的结构体。

    AVStream *in_stream;

    AVCodecContext *dec_ctx, *enc_ctx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。

    AVCodec *encoder;  // AVCodec是存储编解码器信息的结构体。

    int ret;

    unsigned int i;

    ofmt_ctx = NULL;

​

    // avformat_alloc_output_context2 负责分配输出 AVFormatContext。

    // ffmpeg有各种各样的 Context ,其功能是管理各种各样的模块。

    // 例如有一个输出文件:test.mp4,使用 avformat_alloc_output_context2 函数就可以根据文件名分配合适的 AVFormatContext 管理结构。

    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);

    if (!ofmt_ctx) {

        av_log(NULL, AV_LOG_ERROR, "Could not create output context\n"); // 无法创建输出上下文

        return AVERROR_UNKNOWN;

    }

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {

        out_stream = avformat_new_stream(ofmt_ctx, NULL); // 创建输出码流的AVStream

        if (!out_stream) {

            av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n"); // 分配输出流失败

            return AVERROR_UNKNOWN;

        }

        in_stream = ifmt_ctx->streams[i];

        dec_ctx = in_stream->codec;

        enc_ctx = out_stream->codec;

        if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO

                || dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {

            //在这个例子中,我们选择转码到同一个编解码器

            encoder = avcodec_find_encoder(dec_ctx->codec_id); // 查找编码器

            // 在本例中,我们将代码转换为相同的属性(图片大小, 采样率等)。

            // 可以为输出更改这些属性使用过滤器轻松地进行流式处理

            if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { // 解码类型为视频解码

                enc_ctx->height = dec_ctx->height; // 高

                enc_ctx->width = dec_ctx->width;   // 宽

                enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; // 长宽比

                // 从支持的格式列表中获取第一种格式

                enc_ctx->pix_fmt = encoder->pix_fmts[0];

                // 视频时间可以设置为任何方便和编码器支持

                enc_ctx->time_base = dec_ctx->time_base;

            } else { // 音频解码

                enc_ctx->sample_rate = dec_ctx->sample_rate;       // 每秒采样数

                enc_ctx->channel_layout = dec_ctx->channel_layout; // 音频通道布局。

                enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); // 音频通道数

                // 从支持的格式列表中获取第一种格式

                enc_ctx->sample_fmt = encoder->sample_fmts[0];

                AVRational time_base={1, enc_ctx->sample_rate};

                enc_ctx->time_base = time_base;

            }

            // 第三个参数可用于将设置传递给编码器

            ret = avcodec_open2(enc_ctx, encoder, NULL); // 打开编码器

            if (ret < 0) {

                av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i); // 无法打开流的视频编码器

                return ret;

            }

        } else if (dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {

            av_log(NULL, AV_LOG_FATAL, "Elementary stream #%d is of unknown type, cannot proceed\n", i); // 基本流的类型未知,无法继续

            return AVERROR_INVALIDDATA;

        } else {

            // 如果这个流必须被重新计算

            ret = avcodec_copy_context(ofmt_ctx->streams[i]->codec,

                    ifmt_ctx->streams[i]->codec); // avcodec_copy_context :编码参数上下文的拷贝

            if (ret < 0) {

                av_log(NULL, AV_LOG_ERROR, "Copying stream context failed\n"); // 拷贝流上下文失败

                return ret;

            }

        }

        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)

            enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;

    }

    // av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。

    av_dump_format(ofmt_ctx, 0, filename, 1);

    if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {

        ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE); // 该函数用于打开FFmpeg输出文件

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", filename); // 无法打开输出文件

            return ret;

        }

    }

    // 初始化muxer,写入输出文件头

    ret = avformat_write_header(ofmt_ctx, NULL);

    if (ret < 0) {

        av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n"); // 打开输出文件时出错

        return ret;

    }

    return 0;

​

}

​

// 初始化AVFilter相关的结构体。

static int init_filter(FilteringContext* fctx, AVCodecContext *dec_ctx,

        AVCodecContext *enc_ctx, const char *filter_spec)

{

    char args[512];

    int ret = 0;

    AVFilter *buffersrc = NULL;

    AVFilter *buffersink = NULL;

    AVFilterContext *buffersrc_ctx = NULL;

    AVFilterContext *buffersink_ctx = NULL;

    AVFilterInOut *outputs = avfilter_inout_alloc();

    AVFilterInOut *inputs  = avfilter_inout_alloc();

    AVFilterGraph *filter_graph = avfilter_graph_alloc();

    if (!outputs || !inputs || !filter_graph) {

        ret = AVERROR(ENOMEM);

        goto end;

    }

    if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {  // 视频

        buffersrc = avfilter_get_by_name("buffer");

        buffersink = avfilter_get_by_name("buffersink");

        if (!buffersrc || !buffersink) {

            av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); // 未找到筛选源或基本元素

            ret = AVERROR_UNKNOWN;

            goto end;

        }

        _snprintf(args, sizeof(args),

                "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",

                dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,

                dec_ctx->time_base.num, dec_ctx->time_base.den,

                dec_ctx->sample_aspect_ratio.num,

                dec_ctx->sample_aspect_ratio.den);

        // 创建过滤器实例并将其添加到现有的图形中

        ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",

                args, NULL, filter_graph);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); // 无法创建缓冲区源

            goto end;

        }

        // 创建过滤器实例并将其添加到现有的图形中

        ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",

                NULL, NULL, filter_graph);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); // 无法创建缓冲区接收器

            goto end;

        }

        // 用来设置AVOption

        ret = av_opt_set_bin(buffersink_ctx, "pix_fmts",

                (uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt),

                AV_OPT_SEARCH_CHILDREN);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); // 无法设置输出像素格式

            goto end;

        }

    } else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { // 音频

        buffersrc = avfilter_get_by_name("abuffer");

        buffersink = avfilter_get_by_name("abuffersink");

        if (!buffersrc || !buffersink) {

            av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n");  // 未找到筛选源或基本元素

            ret = AVERROR_UNKNOWN;

            goto end;

        }

        if (!dec_ctx->channel_layout)

            dec_ctx->channel_layout =

                av_get_default_channel_layout(dec_ctx->channels); // 音频通道布局

        _snprintf(args, sizeof(args),

                "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%I64x",

                dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate,

                av_get_sample_fmt_name(dec_ctx->sample_fmt),

                dec_ctx->channel_layout);

        // 创建过滤器实例并将其添加到现有的图形中

        ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",

                args, NULL, filter_graph);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n"); // 无法创建音频缓冲区源

            goto end;

        }

        // 创建过滤器实例并将其添加到现有的图形中

        ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",

                NULL, NULL, filter_graph);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n"); // 无法创建音频缓冲区接收器

            goto end;

        }

        // 用来设置AVOption

        ret = av_opt_set_bin(buffersink_ctx, "sample_fmts",

                (uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt),

                AV_OPT_SEARCH_CHILDREN);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n"); // 无法设置输出样本格式

            goto end;

        }

        ret = av_opt_set_bin(buffersink_ctx, "channel_layouts",

                (uint8_t*)&enc_ctx->channel_layout,

                sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n"); // 无法设置输出通道布局

            goto end;

        }

        // 用来设置AVOption

        ret = av_opt_set_bin(buffersink_ctx, "sample_rates",

                (uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate),

                AV_OPT_SEARCH_CHILDREN);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n"); // 无法设置输出采样率

            goto end;

        }

    } else {

        ret = AVERROR_UNKNOWN;

        goto end;

    }

    /* Endpoints for the filter graph. */

    outputs->name       = av_strdup("in");

    outputs->filter_ctx = buffersrc_ctx;

    outputs->pad_idx    = 0;

    outputs->next       = NULL;

    inputs->name       = av_strdup("out");

    inputs->filter_ctx = buffersink_ctx;

    inputs->pad_idx    = 0;

    inputs->next       = NULL;

    if (!outputs->name || !inputs->name) {

        ret = AVERROR(ENOMEM);

        goto end;

    }

    // 将由字符串描述的图形添加到图形中。

    if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_spec,

                    &inputs, &outputs, NULL)) < 0)

        goto end;

    // 检查有效性并配置图中的所有链接和格式。

    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)

        goto end;

    // 给 fctx 进行填充

    fctx->buffersrc_ctx = buffersrc_ctx;

    fctx->buffersink_ctx = buffersink_ctx;

    fctx->filter_graph = filter_graph;

end:

    avfilter_inout_free(&inputs);

    avfilter_inout_free(&outputs);

    return ret;

}

static int init_filters(void)

{

    const char *filter_spec;

    unsigned int i;

    int ret;

    filter_ctx = (FilteringContext *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx));

    if (!filter_ctx)

        return AVERROR(ENOMEM);

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {

        filter_ctx[i].buffersrc_ctx  = NULL;

        filter_ctx[i].buffersink_ctx = NULL;

        filter_ctx[i].filter_graph   = NULL;

        if (!(ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO

                || ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO))

            continue;

        if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)

            filter_spec = "null"; /* 视频直通(虚拟)滤波器 */

        else

            filter_spec = "anull"; /* 视频直通(虚拟)滤波器 */

        ret = init_filter(&filter_ctx[i], ifmt_ctx->streams[i]->codec,

                ofmt_ctx->streams[i]->codec, filter_spec);

        if (ret)

            return ret;

    }

    return 0;

}

// 编码写入帧

static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int *got_frame) {

    int ret;

    int got_frame_local;

    AVPacket enc_pkt;

    int (*enc_func)(AVCodecContext *, AVPacket *, const AVFrame *, int *) =

        (ifmt_ctx->streams[stream_index]->codec->codec_type ==

         AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;

    if (!got_frame)

        got_frame = &got_frame_local;

    av_log(NULL, AV_LOG_INFO, "Encoding frame\n");

    // 编码过滤帧

    enc_pkt.data = NULL;

    enc_pkt.size = 0;

    av_init_packet(&enc_pkt); // 用默认值初始化数据包的可选字段。

    ret = enc_func(ofmt_ctx->streams[stream_index]->codec, &enc_pkt,

            filt_frame, got_frame);

    av_frame_free(&filt_frame); // 释放file_frame

    if (ret < 0)

        return ret;

    if (!(*got_frame))

        return 0;

    // 准备 enc_pkt

    enc_pkt.stream_index = stream_index;

    // av_rescale_q_rnd 将64位整数重缩放为2个具有指定舍入的有理数。

    enc_pkt.dts = av_rescale_q_rnd(enc_pkt.dts,

            ofmt_ctx->streams[stream_index]->codec->time_base,

            ofmt_ctx->streams[stream_index]->time_base,

            (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

    enc_pkt.pts = av_rescale_q_rnd(enc_pkt.pts,

            ofmt_ctx->streams[stream_index]->codec->time_base,

            ofmt_ctx->streams[stream_index]->time_base,

            (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

    enc_pkt.duration = av_rescale_q(enc_pkt.duration,

            ofmt_ctx->streams[stream_index]->codec->time_base,

            ofmt_ctx->streams[stream_index]->time_base);

    av_log(NULL, AV_LOG_DEBUG, "Muxing frame\n");

    /* mux encoded frame */

    ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt); // 将数据包写入输出媒体文件,以确保正确的交织。

    return ret;

}

​

// 过滤编码写入帧

static int filter_encode_write_frame(AVFrame *frame, unsigned int stream_index)

{

    int ret;

    AVFrame *filt_frame;

    av_log(NULL, AV_LOG_INFO, "Pushing decoded frame to filters\n"); // 将解码帧推送到过滤器

    /* 将解码后的帧推入 filtergraph */

    ret = av_buffersrc_add_frame_flags(filter_ctx[stream_index].buffersrc_ctx,

            frame, 0);

    if (ret < 0) {

        av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");

        return ret;

    }

    /* 从 filtergraph 中拉出过滤的帧 */

    while (1) {

        filt_frame = av_frame_alloc(); // 初始化 filt_frame

        if (!filt_frame) {

            ret = AVERROR(ENOMEM);

            break;

        }

        av_log(NULL, AV_LOG_INFO, "Pulling filtered frame from filters\n");

        // av_buffersink_get_frame 从接收器获取一个带有过滤数据的帧,并将其放入帧中

        ret = av_buffersink_get_frame(filter_ctx[stream_index].buffersink_ctx,

                filt_frame);

        if (ret < 0) {

            /* 如果没有更多的输出帧 - returns AVERROR(EAGAIN)

             * 如果刷新并且没有更多的帧用于输出 - returns AVERROR_EOF

             * 将 ret 置为 0 以将其显示为正常过程完成

             */

            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)

                ret = 0;

            av_frame_free(&filt_frame); // 释放 filt_frame

            break;

        }

        filt_frame->pict_type = AV_PICTURE_TYPE_NONE;

        ret = encode_write_frame(filt_frame, stream_index, NULL);

        if (ret < 0)

            break;

    }

    return ret;

}

static int flush_encoder(unsigned int stream_index)

{

    int ret;

    int got_frame;

    if (!(ofmt_ctx->streams[stream_index]->codec->codec->capabilities &

                CODEC_CAP_DELAY))

        return 0;

    while (1) {

        av_log(NULL, AV_LOG_INFO, "Flushing stream #%u encoder\n", stream_index);

        ret = encode_write_frame(NULL, stream_index, &got_frame);

        if (ret < 0)

            break;

        if (!got_frame)

            return 0;

    }

    return ret;

}

​

int _tmain(int argc, _TCHAR* argv[])

{

    int ret;

    AVPacket packet;

    AVFrame *frame = NULL;

    enum AVMediaType type;

    unsigned int stream_index;

    unsigned int i;

    int got_frame;

    int (*dec_func)(AVCodecContext *, AVFrame *, int *, const AVPacket *);

    if (argc != 3) {

        av_log(NULL, AV_LOG_ERROR, "Usage: %s <input file> <output file>\n", argv[0]);

        return 1;

    }

    av_register_all(); // 注册ffmpeg所有编解码器

    avfilter_register_all(); // 初始化过滤系统。注册所有内置过滤器。

    if ((ret = open_input_file(argv[1])) < 0) // 打开输入文件,并初始化相关的结构体。

        goto end;

    if ((ret = open_output_file(argv[2])) < 0) // 打开输出文件,并初始化相关的结构体。

        goto end;

    if ((ret = init_filters()) < 0) // 初始化AVFilter相关的结构体。

        goto end;

    // 读取所有数据包

    while (1) {

        if ((ret = av_read_frame(ifmt_ctx, &packet)) < 0) // 从输入文件中读取一个AVPacket。

            break;

        stream_index = packet.stream_index;

        type = ifmt_ctx->streams[packet.stream_index]->codec->codec_type;

        av_log(NULL, AV_LOG_DEBUG, "Demuxer gave frame of stream_index %u\n",

                stream_index);

        if (filter_ctx[stream_index].filter_graph) {

            av_log(NULL, AV_LOG_DEBUG, "Going to reencode&filter the frame\n");

            frame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存

            if (!frame) {

                ret = AVERROR(ENOMEM);

                break;

            }

            // 将64位整数重缩放为2个具有指定舍入的有理数。

            // 返回重新缩放的值a,或者如果设置了AV\u ROUND\u PASS\u MINMAX并且a是INT64_MIN或INT64_MAX则a以不变的方式通过。

            packet.dts = av_rescale_q_rnd(packet.dts,

                    ifmt_ctx->streams[stream_index]->time_base,

                    ifmt_ctx->streams[stream_index]->codec->time_base,

                    (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

            packet.pts = av_rescale_q_rnd(packet.pts,

                    ifmt_ctx->streams[stream_index]->time_base,

                    ifmt_ctx->streams[stream_index]->codec->time_base,

                    (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

            // 视频解码或者音频解码

            dec_func = (type == AVMEDIA_TYPE_VIDEO) ? avcodec_decode_video2 :

                avcodec_decode_audio4;

            ret = dec_func(ifmt_ctx->streams[stream_index]->codec, frame,

                    &got_frame, &packet);

            if (ret < 0) {

                av_frame_free(&frame);

                av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");

                break;

            }

            if (got_frame) {

                frame->pts = av_frame_get_best_effort_timestamp(frame);

                ret = filter_encode_write_frame(frame, stream_index); // 编码一个AVFrame。

                av_frame_free(&frame);

                if (ret < 0)

                    goto end;

            } else {

                av_frame_free(&frame);

            }

        } else {

            // 重新复制此帧而不重新编码

            packet.dts = av_rescale_q_rnd(packet.dts,

                    ifmt_ctx->streams[stream_index]->time_base,

                    ofmt_ctx->streams[stream_index]->time_base,

                     (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

            packet.pts = av_rescale_q_rnd(packet.pts,

                    ifmt_ctx->streams[stream_index]->time_base,

                    ofmt_ctx->streams[stream_index]->time_base,

                     (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

            ret = av_interleaved_write_frame(ofmt_ctx, &packet); // 将数据包写入输出媒体文件,以确保正确的交织。

            if (ret < 0)

                goto end;

        }

        av_free_packet(&packet);

    }

    /* flush filters and encoders */

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {

        /* flush filter */

        if (!filter_ctx[i].filter_graph)

            continue;

        ret = filter_encode_write_frame(NULL, i); // 编码一个AVFrame。

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Flushing filter failed\n");

            goto end;

        }

        /* flush encoder */

        ret = flush_encoder(i);

        if (ret < 0) {

            av_log(NULL, AV_LOG_ERROR, "Flushing encoder failed\n");

            goto end;

        }

    }

    av_write_trailer(ofmt_ctx); // 写文件尾

end:

    av_free_packet(&packet);

    av_frame_free(&frame);

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {

        avcodec_close(ifmt_ctx->streams[i]->codec);

        if (ofmt_ctx && ofmt_ctx->nb_streams > i && ofmt_ctx->streams[i] && ofmt_ctx->streams[i]->codec)

            avcodec_close(ofmt_ctx->streams[i]->codec);

        if (filter_ctx && filter_ctx[i].filter_graph)

            avfilter_graph_free(&filter_ctx[i].filter_graph);

    }

    av_free(filter_ctx);

    avformat_close_input(&ifmt_ctx);

    if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))

        avio_close(ofmt_ctx->pb);

    avformat_free_context(ofmt_ctx);

    if (ret < 0)

        av_log(NULL, AV_LOG_ERROR, "Error occurred\n");

    return (ret? 1:0);

2.8 YUV讲解

2.8.1 什么是YUV

        YUV,是一种颜色编码方法,“Y”表示明亮度,也就是灰阶值,“U”和“V”表示的则是色度,作用是描述影像色彩及饱和度,用于指定像素的颜色。

2.8.2 常见的YUV格式

YUV420格式:4:2:0表示2:1的水平取样,垂直2:1采样。

此外还有:

4:4:4表示完全取样。

4:2:2表示2:1的水平取样,垂直完全采样。

4:1:1表示4:1的水平取样,垂直完全采样。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1549671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年云计算使用报告,89%组织用多云,25%广泛使用生成式AI,45%需要跨云数据集成,节省成本是云首要因素

备注&#xff1a;本文来自Flexera2024年的云现状调研报告的翻译。原报告地址&#xff1a; https://info.flexera.com/CM-REPORT-State-of-the-Cloud Flexera是一家专注于做SaaS的IT解决方案公司&#xff0c;有30年发展历史&#xff0c;5万名客户&#xff0c;1300名员工。Flex…

备考ICA----Istio实验10---为单个主机配置TLS Istio Ingress Gateway实验

备考ICA----Istio实验10—为单个主机配置 TLS Istio Ingress Gateway实验 1. 环境准备 部署httpbin kubectl apply -f istio/samples/httpbin/httpbin.yaml 2. 证书生成 2.1 生成根证书 生成根证书keyfile和crt文件 mkdir example_certs_root openssl req -x509 -sha256 …

mac-git上传至github(ssh版本,个人tokens总出错)

第一步 git clone https://github.com/用户名/项目名.git 第二步 cd 项目名 第三步 将本地的文件移动到项目下 第四步 git add . 第五步 git commit -m "添加****文件夹" 第六步 git push origin main 报错&#xff1a; 采用ssh验证 本地文件链接公钥 …

【机器学习300问】53、什么组合特征?为什么要组合特征?

一、什么是组合特征&#xff1f; 组合特征是指在机器学习通过将两个或多个基础特征进行某种形式的结合而创建的新特征。这些新特征是描述数据的新视角&#xff0c;这有助于模型发现和学习数据中更复杂的模式。 例如&#xff0c;在广告点击预测问题中&#xff0c;我们有两个基础…

协程库-锁类-实现线程互斥同步

mutex.h&#xff1a;信号量&#xff0c;互斥锁&#xff0c;读写锁&#xff0c;范围锁模板&#xff0c;自旋锁&#xff0c;原子锁 锁 **锁不能进行拷贝操作&#xff1a;**锁是用于管理多线程并发访问共享资源的同步原语。这些锁包括互斥锁&#xff08;mutex&#xff09;、读写锁…

数仓建设实践——58用户画像数仓建设

目录 一、数据仓库&用户画像简介 1.1 数据仓库简介 1.2 数据仓库的价值 1.3 用户画像简介 1.4 用户画像—标签体系 二、用户画像数仓建设过程 2.1 画像数仓—背景&现状 2.2 画像数仓—整体架构 2.3 画像数仓—研发流程 2.4 画像数仓—指标定义 2.5 画像数仓…

Day50:WEB攻防-PHP应用文件包含LFIRFI伪协议编码算法无文件利用黑白盒

目录 文件包含-原理&分类&利用&修复 文件读取 文件写入 代码执行 远程利用思路 黑盒利用-VULWEB 白盒利用-CTFSHOW-伪协议玩法 78-php&http协议 79-data&http协议 80-81-日志包含 87-php://filter/write&加密编码 88-data&base64协议 …

【深度学习】【机器学习】用神经网络进行入侵检测,NSL-KDD数据集,基于机器学习(深度学习)判断网络入侵

文章目录 下载数据集NSL-KDD数据集介绍输入的41个特征输出的含义数据处理&&训练技巧建神经网络&#xff0c;输入41个特征&#xff0c;输出是那种类别的攻击模型训练模型推理写gradio前端界面&#xff0c;用户自己输入41个特征&#xff0c;后端用模型推理计算后显示出是…

银行卡的分类

银行卡是银行账户的一种体现形式&#xff0c;它是由银行机构发行的具有消费信用、转账结算、存取现金等全部或部分功能作为结算支付工具的各类卡的统称。 &#xff08;1&#xff09;按是否具有授信额度分类 ①借记卡&#xff1a;借记卡是指发卡银行向申请人签发的&#xff0c;没…

牛客NC79 丑数【中等 堆、优先级队列 Java,Go,PHP Go和PHP中我自己实现了优先级队列】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b 思路 注意&#xff1a; 数据范围&#xff1a;0≤n≤2000&#xff0c; 2000肯定到不了&#xff0c;最多到1690&#xff0c;相同题目链接&#xff1a;https://www.lintcode.com…

netty构建udp服务器以及发送报文到客户端客户端详细案例

目录 一、基于netty创建udp服务端以及对应通道设置关键 二、发送数据 三、netty中的ChannelOption常用参数说明 1、ChannelOption.SO_BACKLOG 2、ChannelOption.SO_REUSEADDR 3、ChannelOption.SO_KEEPALIVE 4、ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF 5、Ch…

CUDA安装 Windows版

目录 一、说明 二、安装工具下载 三、CUDA安装 四、cuDNN配置 五、验证安装是否成功 一、说明 windows10 版本安装 CUDA &#xff0c;首先需要下载两个安装包 CUDA toolkitcuDNN 官方教程 CUDA&#xff1a;https://docs.nvidia.com/cuda/cuda-installation-guide-micro…

2.2 添加商户缓存

实战篇Redis 2.2 添加商户缓存 在我们查询商户信息时&#xff0c;我们是直接操作从数据库中去进行查询的&#xff0c;大致逻辑是这样&#xff0c;直接查询数据库那肯定慢咯&#xff0c;所以我们需要增加缓存 GetMapping("/{id}") public Result queryShopById(Pat…

政安晨:【深度学习神经网络基础】(一)—— 逐本溯源

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 与计算机一样的古老历史 神经网络的出现可追溯到20世纪40年…

Android源码阅读WorkMangaer - 6

前言 由于笔者目前水平限制&#xff0c;表达能力有限&#xff0c;尽请见谅。 WorkManager 是 Android Jetpack 库的一部分&#xff0c;提供了一种向后兼容的方式来安排可延迟的异步任务&#xff0c;这些任务即使在应用退出或设备重启后也应该继续执行&#xff0c;它是 Androi…

记录 AI绘图 Stable Diffusion的本地安装使用,可搭建画图服务端

开头 最近刷短视频看到了很多关于AI绘图&#xff0c;Midjourney&#xff0c;gittimg.ai&#xff0c;Stable Diffusion等一些绘图AI工具&#xff0c;感受到了AI绘画的魅力。通过chatGPT生成关键词再加上绘图工具&#xff0c;真是完美&#xff0c;文末教大家如何用gpt提词 Midj…

Anaconda的GEE环境中安装torch库

打开Anaconda&#xff0c;点击运行&#xff0c;打开terminal 输入pip install torch 而且由于anaconda中自己配置好了镜像源&#xff0c;在pip时自动使用清华镜像源

2024年4月份 风车IM即时通讯系统APP源码 版完整苹果安卓教程

关于风车IM&#xff0c;你在互联网上能随便下载到了基本都是残缺品&#xff0c; 经过我们不懈努力最终提供性价比最高&#xff0c;最完美的版本&#xff0c; 懂货的朋友可以直接下载该版本使用&#xff0c;经过严格测试&#xff0c;该版本基本完美无缺。 下载地址&#xff1a;…

【正点原子FreeRTOS学习笔记】————(4)FreeRTOS中断管理

这里写目录标题 一、什么是中断&#xff1f;&#xff08;了解&#xff09;二、中断优先级分组设置&#xff08;熟悉&#xff09;三、中断相关寄存器&#xff08;熟悉&#xff09;四、FreeRTOS中断管理实验&#xff08;掌握&#xff09; 一、什么是中断&#xff1f;&#xff08;…

华为数通 HCIP-Datacom H12-831 题库补充(3/27)

2024年 HCIP-Datacom&#xff08;H12-831&#xff09;最新题库&#xff0c;完整题库请扫描上方二维码&#xff0c;持续更新。 如图所示&#xff0c;关于R4路由器通过IS-IS计算出来的IPv6路由&#xff0c;哪一选项的描述是错误的&#xff1f; A&#xff1a;R4通过IS—IS只学习到…