FFmpeg初步了解

一、了解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的水平取样,垂直完全采样。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

07_Response

文章目录 案例&#xff08;请求分发案例&#xff09; Response响应行响应头响应体特殊响应头refreshContent-typeContent-dispositionlocation 案例&#xff08;登录案例&#xff09; 案例&#xff08;请求分发案例&#xff09; 场景&#xff1a;有多个请求 Http://localhost:…

python Flask扩展:如何查找高效开发的第三方模块(库/插件)

如何找到扩展以及使用扩展的文档 一、背景二、如何寻找框架的扩展&#xff1f;三、找到想要的扩展四、找到使用扩展的文档五、项目中实战扩展 一、背景 刚入门python的flask的框架&#xff0c;跟着文档学习了一些以后&#xff0c;想着其实在项目开发中&#xff0c;经常会用到发…

设计模式之建造者模式精讲

也叫生成器模式。将一个复杂的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 在建造者模式中&#xff0c;有如下4个角色&#xff1a; 抽象建造者&#xff08;Builder&#xff09;&#xff1a;用于规范产品的各个组成部分&#xff0c;并进行抽象&…

HTTP请求走私!!!(一)

想都是问题&#xff0c;做才是答案 什么是请求走私&#xff1f; HTTP请求走私是针对于服务端处理一个或者多个接收http请求序列的方式&#xff0c;进行绕过安全机制&#xff0c;实施未授权访问一种攻击手段&#xff0c;获取敏感信息&#xff0c;并直接危害其他用户。 Web 应用…

集合系列(十五) -CopyOnWriteArrayList详解

一、摘要 在介绍 CopyOnWriteArrayList 之前&#xff0c;我们一起先来看看如下方法执行结果&#xff0c;代码内容如下&#xff1a; public static void main(String[] args) {List<String> list new ArrayList<String>();list.add("1");list.add(&quo…

RabbitMQ(简单模式)

2种远程服务调用 1openFeign&#xff1a; 优点&#xff1a;能拿到被调用的微服务返回的数据&#xff0c;系统系耦度高&#xff0c;系统稳定。 缺点&#xff1a;同步调用&#xff0c;如果有很多服务需要被调用&#xff0c;耗时长。 MQ,消息队列&#xff0c;RabbitMQ是消息we…

windows DNS 服务器常用配置

Windows Server 上的 DNS 服务器配置主要包括以下步骤和常见设置&#xff1a; 安装 DNS 服务 打开服务器管理器&#xff0c;在 "角色和功能" 添加或删除角色中安装 DNS 服务器角色。 配置 DNS 服务器基础设置 添加 DNS 区域&#xff1a; 在 DNS 管理器中&#xff…

WPF 窗体样式 WindowStyle 设置 none, 如何移动窗体

当窗体样式设为None时&#xff0c;因为标题栏被隐藏&#xff0c;我们无法移动窗体&#xff0c;为了使窗体可以移动&#xff0c;我们要为window中的布局控件添加MouseDown事件的事件处理器&#xff0c;并且要为其设置一个背景(window设置为允许透明&#xff0c;当grid没有背景时…

书籍推荐|meta分析R语言实践教程-Doing Meta-Analysis with R: A Hands-On Guide

“The problems are solved, not by giving new information, but by arranging what we have known since long.” – Ludwig Wittgenstein 推荐理由 《Doing Meta-Analysis with R: A Hands-On Guide》是由 Mathias Harrer, Pim Cuijpers, Toshi Furukawa, 和 David Ebert所…

Hides for Mac:应用程序隐藏工具

Hides for Mac是一款功能强大的应用程序隐藏工具&#xff0c;专为Mac用户设计。它能够帮助用户快速隐藏当前正在运行的应用程序窗口&#xff0c;保护用户的隐私和工作内容&#xff0c;避免不必要的干扰。 软件下载&#xff1a;Hides for Mac下载 Hides for Mac的使用非常简单直…

Windows下载使用nc(netcat)命令

‘nc’ 不是内部或外部命令&#xff0c;也不是可运行的程序&#xff1f; 点击链接地址&#xff0c;下载压缩包。 完成后解压 使用方式&#xff08;三种&#xff09;&#xff1a; 1、直接双击exe使用 2、把这个exe放到cmd启动的默认路径下 放到默认路径下&#xff0c;使用nc&a…

0.96寸OLED屏调试 ----(一)

所需设备&#xff1a; 1、USB 转 SPI I2C 适配器&#xff1b;内附链接 2、0.96寸OLED显示模块&#xff1b; 备注&#xff1a;专业版、升级版都适用&#xff1b; 首先介绍一下OLED显示模块&#xff0c;SSD1306是一款OLED驱动芯片&#xff0c;拥有最大128*64像素支持&#xff…

亚马逊云科技:基于老服务器打造的旧实例类型

内容摘要&#xff1a; 2021年&#xff0c;距离第一个EC2实例上线已经十五周年了。 在漫长的开发过程中&#xff0c;很多EC2实例自然会基于旧服务器构建。 随着时间的推移&#xff0c;旧的服务器总是需要更换硬件&#xff0c;实例也得更换&#xff0c;但并不是所有的用户都想迁…

腾讯云免费云服务器申请流程详解

随着云计算的普及&#xff0c;越来越多的企业和个人开始选择使用云服务器。腾讯云作为国内领先的云计算服务提供商&#xff0c;为用户提供了丰富的云产品和服务。本文将为大家详细介绍腾讯云免费云服务器的申请流程。 一、注册腾讯云账号 首先&#xff0c;需要注册一个腾讯云账…

【氮化镓】镁激活退火对p-GaN迁移率和阈值电压的影响

【Mg activation anneal of the p-GaN body in trench gate MOSFETs and its effect on channel mobility and threshold voltage stability】 文献总结&#xff1a; 本研究探讨了在沟道栅MOSFETs中&#xff0c;镁&#xff08;Mg&#xff09;激活退火步骤对p型氮化镓&#xf…

python opencv稍基础初学

傅里叶变换 傅里叶变换f​​​​​傅里叶分析之掐死教程&#xff08;完整版&#xff09;更新于2014.06.06 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/19763358 相当nice 傅里叶变换的作用 高频&#xff1a;变化剧烈的灰度分量&#xff0c;例如边界 低频&#xff1a;变…

如何使用极狐GitLab 自定义 Pages 根域名

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了极狐GitLab Pages …

【Ollama】AI大模型本地部署

Ollama Ollama轻量级、可扩展的框架&#xff0c;用于在本地机器上构建和运行大型语言模型&#xff08;LLM&#xff09;。提供了一个简单的API来创建、运行和管理模型&#xff0c;以及一个预构建模型库&#xff0c;可以轻松用于各种应用程序。 明显优势&#xff1a;易于使用、…

大模型prompt工程学习(一)

目录 调prompt的方法 prompt时好时不好 大模型本质是没有记忆的 划重点:我们发给大模型的 prompt&#xff0c;不会改变大模型的参数 ГLets think step by step」 一步步分析一下 自洽性&#xff0c;同时跑多次&#xff0c;来减少幻觉 逻辑&#xff0c;基本能力来是要有…

【数据结构】计数排序

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解计数排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 计数排序代码 一. 基本思想 计数排序又称为鸽巢原理&#xff0c;是对哈希直…