【音视频】音频编码实战
FFmpeg流程
从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。
示例的流程如下所示。
关键函数说明:
- avcodec_find_encoder:根据指定的AVCodecID查找注册的编码器。
- avcodec_alloc_context3:为AVCodecContext分配内存。
- avcodec_open2:打开编码器。
- avcodec_send_frame:将AVFrame⾮压缩数据给编码器。
- avcodec_receive_packet:获取到编码后的AVPacket数据,收到的packet需要⾃⼰释放内存。
- av_frame_get_buffer: 为⾳频或视频帧分配新的buffer。在调⽤这个函数之前,必须在AVFame上设置好以下属性:format(视频为像素格式,⾳频为样本格式)、nb_samples(样本个数,针对⾳频)、channel_layout(通道类型,针对⾳频)、width/height(宽⾼,针对视频)。
- av_frame_make_writable:确保AVFrame是可写的,使⽤av_frame_make_writable()的问题是,在最坏的情况下,它会在您使⽤encode再次更改整个输⼊frame之前复制它. 如果frame不可写,av_frame_make_writable()将分配新的缓冲区,并复制这个输⼊input frame数据,避免和编码器需要缓存该帧时造成冲突。
- av_samples_fill_arrays 填充⾳频帧
对于 flush encoder的操作:
编码器通常的冲洗⽅法:调⽤⼀次 avcodec_send_frame(NULL)(返回成功),然后不停调⽤
avcodec_receive_packet() 直到其返回 AVERROR_EOF,取出所有缓存帧,avcodec_receive_packet() 返回 AVERROR_EOF 这⼀次是没有有效数据的,仅仅获取到⼀个结束标志
PCM样本格式
PCM(Pulse Code Modulation,脉冲编码调制)⾳频数据是未经压缩的⾳频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字⾳频数据。
描述PCM数据的6个参数:
- Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。
- Sample Size : 量化位数。通常该值为16-bit。
- Number of Channels : 通道个数。常⻅的⾳频有⽴体声(stereo)和单声道(mono)两种类型,⽴体声包
含左声道和右声道。另外还有环绕⽴体声等其它不太常⽤的类型。 - Sign : 表示样本数据是否是有符号位,⽐如⽤⼀字节表示的样本数据,有符号的话表示范围为-128 ~
127,⽆符号是0 ~ 255。有符号位16bits数据取值范围为-32768~32767。 - Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。字节序说明⻅第4节。
- Integer Or Floating Point : 整形或浮点型。⼤多数格式的PCM样本数据使⽤整形表示,⽽在⼀些对精度要求⾼的应⽤⽅⾯,使⽤浮点类型表示PCM样本数据(浮点数 float值域为
[-1.0, 1.0]
)。
FFmpeg⽀持的PCM数据格式
使⽤ffmpeg -formats命令,获取ffmpeg⽀持的⾳视频格式,其中我们可以找到⽀持的PCM格式。
- s是有符号,u是⽆符号,f是浮点数。
- be是⼤端,le是⼩端。
DE alaw PCM A-law
DE f32be PCM 32-bit floating-point big-endian
DE f32le PCM 32-bit floating-point little-endian
DE f64be PCM 64-bit floating-point big-endian
DE f64le PCM 64-bit floating-point little-endian
DE mulaw PCM mu-law
DE s16be PCM signed 16-bit big-endian
DE s16le PCM signed 16-bit little-endian
DE s24be PCM signed 24-bit big-endian
DE s24le PCM signed 24-bit little-endian
DE s32be PCM signed 32-bit big-endian
DE s32le PCM signed 32-bit little-endian
DE s8 PCM signed 8-bit
DE u16be PCM unsigned 16-bit big-endian
DE u16le PCM unsigned 16-bit little-endian
DE u24be PCM unsigned 24-bit big-endian
DE u24le PCM unsigned 24-bit little-endian
DE u32be PCM unsigned 32-bit big-endian
DE u32le PCM unsigned 32-bit little-endian
DE u8 PCM unsigned 8-bit
FFmpeg中Packed和Planar的PCM数据区别
FFmpeg中⾳视频数据基本上都有Packed和Planar两种存储⽅式,对于双声道⾳频来说,Packed⽅式为两个声道的数据交错存储;Planar⽅式为两个声道分开存储。假设⼀个L/R为⼀
个采样点,数据存储的⽅式如下所示:
- Packed: L R L R L R L R
- Planar: L L L L … R R R R…
packed格式
AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
AV_SAMPLE_FMT_S16, ///< signed 16 bits
AV_SAMPLE_FMT_S32, ///< signed 32 bits
AV_SAMPLE_FMT_FLT, ///< float
AV_SAMPLE_FMT_DBL, ///< double
只能保存在AVFrame
的uint8_t *data[0]
;
⾳频保持格式如下:
LRLRLR...
planar格式
planar为FFmpeg内部存储⾳频使⽤的采样格式,所有的Planar格式后⾯都有字⺟P标识。
AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
AV_SAMPLE_FMT_FLTP, ///< float, planar
AV_SAMPLE_FMT_DBLP, ///< double, planar
AV_SAMPLE_FMT_S64, ///< signed 64 bits
AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
-
plane 0:
LLLLLLLLLLLLLLLLLLLLLLLLLL...
-
plane 1:
RRRRRRRRRRRRRRRRRRRRRRRRRR...
-
plane 0对应
uint8_t *data[0]
-
plane1对应
uint8_t *data[1]
FFmpeg默认的AAC编码器不⽀持AV_SAMPLE_FMT_S16格式的编码,只⽀持AV_SAMPLE_FMT_FLTP
,这种格式是按平⾯存储,样点是float
类型,所谓平⾯也就是每个声道单独存储,⽐如左声道存储到data[0]
中,右声道存储到data[1]
中。
FFmpeg⾳频解码后和编码前的数据是存放在AVFrame结构中的。
Packed
格式,frame.data[0]
或frame.extended_data[0]
包含所有的⾳频数据中。Planar
格式,frame.data[i]
或者frame.extended_data[i]
表示第i
个声道的数据(假设声道0
是第⼀个), AVFrame.data数组⼤⼩固定为8,如果声道数超过8
,需要从frame.extended_data
获取声道数据。
补充说明
-
Planar模式是ffmpeg内部存储模式,我们实际使⽤的⾳频⽂件都是Packed模式的。
-
FFmpeg解码不同格式的⾳频输出的⾳频采样格式不是⼀样。测试发现,其中AAC解码输出的数据为浮点型的
AV_SAMPLE_FMT_FLTP
格式,MP3
解码输出的数据为AV_SAMPLE_FMT_S16P
格式(使⽤的mp3⽂件为16位深)。具体采样格式可以查看解码后的AVFrame
中的format
成员或编解码器的AVCodecContex
t中的sample_fmt
成员。 -
Planar或者Packed模式直接影响到保存⽂件时写⽂件的操作,操作数据的时候⼀定要先检测⾳频采样格式。
PCM字节序
谈到字节序的问题,必然牵涉到两⼤CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采⽤big endian⽅式存储数据,⽽x86系列则采⽤little endian⽅式存储数据。
那么究竟什么是big endian,什么⼜是little endian?
- big endian是指低地址存放最⾼有效字节(MSB,Most Significant Bit),⽽little endian则是低地址存放最低有效字节(LSB,Least Significant Bit)。
下⾯⽤图像加以说明。⽐如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示
Big Endian
----------------------------------------------------------------------------->| 12 | 34 | 56 | 78 |低地址 ⾼地址----------------------------------------------------------------------------->
Little Endian
----------------------------------------------------------------------------->| 78 | 56 | 34 | 12 |Little Endian低地址 ⾼地址----------------------------------------------------------------------------->
所有⽹络协议都是采⽤big endian的⽅式来传输数据的。所以也把big endian⽅式称之为⽹络字节序。当两台采⽤不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为⽹络字节序后再进⾏传输。
实现流程
添加pcm
文件
我们需要将pcm
编码为aac
,在build
路径下添加相应的pcm
文件
配置编码器参数
- 我们的
pcm
文件使用的f32le
格式,但是因为ffmpeg
默认的AAC
编码器只支持fltp
格式,因此我们这里设置参数要设置为fltp
格式 - 设置编码类型、编码器id、采样率、通道数、比特流等信息
- 使用
libfdk-aac
可以编码更多音频类型的aac
格式,并且编码后的aac
自带adts
- 设置编码器
flag
为AV_CODEC_FLAG_GLOBAL_HEADER
,表示生成的aac
没有头部,头部数据存储AVCodecContext
的extradata
中
const AVCodec *codec = NULL;
AVCodecContext *codec_ctx= NULL;enum AVCodecID codec_id = AV_CODEC_ID_AAC;
codec = avcodec_find_encoder(codec_id);
codec_ctx = avcodec_alloc_context3(codec);codec_ctx->codec_id = codec_id;
codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
codec_ctx->bit_rate = 128*1024;
codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
codec_ctx->sample_rate = 48000; //48000;
codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);
codec_ctx->profile = FF_PROFILE_AAC_LOW; codec_ctx->flags = AV_CODEC_FLAG_GLOBAL_HEADER;
- 查找编码器有两种方式,一种是使用名字,一种是使用
id
codec = avcodec_find_encoder_by_name(codec_name);//使用名字
codec = avcodec_find_encoder(codec_id);//使用id
检查编码器支持
- 根据输入参数,检查编码器是否支持这些参数(如采样率,音频格式,通道格式等)
/* 检测支持采样格式支持情况 */
if (!check_sample_fmt(codec, codec_ctx->sample_fmt)) {fprintf(stderr, "Encoder does not support sample format %s",av_get_sample_fmt_name(codec_ctx->sample_fmt));exit(1);
}
if (!check_sample_rate(codec, codec_ctx->sample_rate)) {fprintf(stderr, "Encoder does not support sample rate %d", codec_ctx->sample_rate);exit(1);
}
if (!check_channel_layout(codec, codec_ctx->channel_layout)) {fprintf(stderr, "Encoder does not support channel layout %lu", codec_ctx->channel_layout);exit(1);
}
check_sample_fmt
函数,检查支持的音频格式- 通过
codec->sample_fmts
指针,实际上是连续的数组,来遍历所有的类型验证
/* 检测该编码器是否支持该采样格式 */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{const enum AVSampleFormat *p = codec->sample_fmts;while (*p != AV_SAMPLE_FMT_NONE) { // 通过AV_SAMPLE_FMT_NONE作为结束符if (*p == sample_fmt)return 1;p++;}return 0;
}
``
check_sample_rate
函数,检查支持的采样率
/* 检测该编码器是否支持该采样率 */
static int check_sample_rate(const AVCodec *codec, const int sample_rate)
{const int *p = codec->supported_samplerates;while (*p != 0) {// 0作为退出条件,比如libfdk-aacenc.c的aac_sample_ratesprintf("%s support %dhz\n", codec->name, *p);if (*p == sample_rate)return 1;p++;}return 0;
}
check_channel_layout
函数,用于检查支持的通道数
/* 检测该编码器是否支持该通道数, 该函数只是作参考 */
static int check_channel_layout(const AVCodec *codec, const uint64_t channel_layout)
{// 不是每个codec都给出支持的channel_layoutconst uint64_t *p = codec->channel_layouts;if(!p) {printf("the codec %s no set channel_layouts\n", codec->name);return 1;}while (*p != 0) { // 0作为退出条件,比如libfdk-aacenc.c的aac_channel_layoutprintf("%s support channel_layout %d\n", codec->name, *p);if (*p == channel_layout)return 1;p++;}return 0;
}
- 设置好编码器上下文参数后,将编码器和上下文关联起来
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);
}
打开文件
- 打开输入的
pcm
文件和输出的aac
文件
FILE *infile = NULL;
FILE *outfile = NULL;infile = fopen(in_pcm_file, "rb");
if (!infile) {fprintf(stderr, "Could not open %s\n", in_pcm_file);exit(1);
}
outfile = fopen(out_aac_file, "wb");
if (!outfile) {fprintf(stderr, "Could not open %s\n", out_aac_file);exit(1);
}
设置编码帧frame
参数
- 设置裸数据帧的
frame
参数 - 主要是设置采样点、采样率、音频格式、通道布局、通道数等参数
frame_size
是在解码器和解码器上下问关联后才获得的,表示一帧的采样点数
AVFrame* frame = NULL;
frame = av_frame_alloc();frame->nb_samples = codec_ctx->frame_size;
frame->format = codec_ctx->sample_fmt;
frame->channel_layout = codec_ctx->channel_layout;
frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);
- 根据每一帧的数据大小,分配缓冲区
- 缓冲区大小为:每个样本的字节数 * 声道数 * 样本数
int frame_bytes = av_get_bytes_per_sample(frame->format) * frame->channels * frame->nb_samples;
uint8_t *pcm_buf = (uint8_t *)malloc(frame_bytes);
if(!pcm_temp_buf) {printf("pcm_temp_buf malloc failed\n");return 1;
}
循环编码写入文件
- 编码前需要清空之前的缓冲区内存
memset(pcm_buf, 0, frame_bytes);
- 从
pcm
文件中读取相应的一帧大小的数据
size_t read_bytes = fread(pcm_buf, 1, frame_bytes, infile);if(read_bytes <= 0) {printf("read file finish\n");break;}
- 写入之前,需要保证
frame
可写,因为一些编码器(如h264
编码器),可能会引用一些帧,导致frame
不可写,因此需要先保证帧可写
ret = av_frame_make_writable(frame);
if(ret != 0)printf("av_frame_make_writable failed, ret = %d\n", ret);
- 转换音频格式为
fltp
,因为ffmpeg
默认编码器只支持fltp
- 拷贝一份缓冲区,用于转换数据存储,将原始数据
buf
转换并存储到temp_buf
中 av_samples_fill_arrays
函数将音频数据pcm_temp_buf
拷贝到frame->data
的数据数组中
memset(pcm_temp_buf, 0, frame_bytes);
f32le_convert_to_fltp((float *)pcm_buf, (float *)pcm_temp_buf, frame->nb_samples);
ret = av_samples_fill_arrays(frame->data, frame->linesize,pcm_temp_buf, frame->channels,frame->nb_samples, frame->format, 0);
f32le_convert_to_fltp
函数用于音频格式转换
- 主要是将打包模式转为平面模式
- 即
LLLLRRRR -> LRLRLRLR
void f32le_convert_to_fltp(float *f32le, float *fltp, int nb_samples) {float *fltp_l = fltp; // 左通道float *fltp_r = fltp + nb_samples; // 右通道for(int i = 0; i < nb_samples; i++) {fltp_l[i] = f32le[i*2]; // 0 1 - 2 3fltp_r[i] = f32le[i*2+1]; // 可以尝试注释左声道或者右声道听听声音}
}
- 设置写入的
frame
的pts
时间戳,这里的pts
单位为采样点
int64_t pts = 0;
pts += frame->nb_samples;
frame->pts = pts; // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
- 将
frame
编码为packet
,并且写入文件中
ret = encode(codec_ctx, frame, pkt, outfile);
if(ret < 0) {printf("encode failed\n");break;
}
编码函数encode
- 主要是使用
avcodec_send_frame
和avcodec_receive_packet
,送入裸数据frame
,然后编码为aac
包packet
- 一个
frame
可能编码出多个packet
,因此需要循环接收,根据返回值来判断 - 需要先写入
adts
头部,然后再写入aac
的packet
数据
static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *output)
{int ret;/* send the frame for encoding */ret = avcodec_send_frame(ctx, frame);if (ret < 0) {fprintf(stderr, "Error sending the frame to the encoder\n");return -1;}/* read all the available output packets (in general there may be any number of them */// 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOFwhile (ret >= 0) {ret = avcodec_receive_packet(ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {fprintf(stderr, "Error encoding audio frame\n");return -1;}size_t len = 0;printf("ctx->flags:0x%x & AV_CODEC_FLAG_GLOBAL_HEADER:0x%x, name:%s\n",ctx->flags, ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER, ctx->codec->name);if((ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER)) {// 需要额外的adts header写入uint8_t aac_header[7];get_adts_header(ctx, aac_header, pkt->size);len = fwrite(aac_header, 1, 7, output);if(len != 7) {fprintf(stderr, "fwrite aac_header failed\n");return -1;}}len = fwrite(pkt->data, 1, pkt->size, output);if(len != pkt->size) {fprintf(stderr, "fwrite aac data failed\n");return -1;}/* 是否需要释放数据? avcodec_receive_packet第一个调用的就是 av_packet_unref* 所以我们不用手动去释放,这里有个问题,不能将pkt直接插入到队列,因为编码器会释放数据* 可以新分配一个pkt, 然后使用av_packet_move_ref转移pkt对应的buffer*/// av_packet_unref(pkt);}return -1;
}
get_adts_header
函数,用于写入adts
数据
static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
{uint8_t freq_idx = 0; //0: 96000 Hz 3: 48000 Hz 4: 44100 Hzswitch (ctx->sample_rate) {case 96000: freq_idx = 0; break;case 88200: freq_idx = 1; break;case 64000: freq_idx = 2; break;case 48000: freq_idx = 3; break;case 44100: freq_idx = 4; break;case 32000: freq_idx = 5; break;case 24000: freq_idx = 6; break;case 22050: freq_idx = 7; break;case 16000: freq_idx = 8; break;case 12000: freq_idx = 9; break;case 11025: freq_idx = 10; break;case 8000: freq_idx = 11; break;case 7350: freq_idx = 12; break;default: freq_idx = 4; break;}uint8_t chanCfg = ctx->channels;uint32_t frame_length = aac_length + 7;adts_header[0] = 0xFF;adts_header[1] = 0xF1;adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);adts_header[3] = (((chanCfg & 3) << 6) + (frame_length >> 11));adts_header[4] = ((frame_length & 0x7FF) >> 3);adts_header[5] = (((frame_length & 7) << 5) + 0x1F);adts_header[6] = 0xFC;
}
冲刷编码器
- 编码结束后,需要冲刷编码器将剩余数据写入输出文件中
/* 冲刷编码器 */
encode(codec_ctx, NULL, pkt, outfile);
结束工作
- 最后需要释放内存,关闭文件
// 关闭文件fclose(infile);fclose(outfile);// 释放内存if(pcm_buf) {free(pcm_buf);}if (pcm_temp_buf) {free(pcm_temp_buf);}av_frame_free(&frame);av_packet_free(&pkt);avcodec_free_context(&codec_ctx);
整体代码
main.c
/**
* @projectName 08-01-encode_audio
* @brief 音频编码
* 从本地读取PCM数据进行AAC编码
* 1. 输入PCM格式问题,通过AVCodec的sample_fmts参数获取具体的格式支持
* (1)默认的aac编码器输入的PCM格式为:AV_SAMPLE_FMT_FLTP
* (2)libfdk_aac编码器输入的PCM格式为AV_SAMPLE_FMT_S16.
* 2. 支持的采样率,通过AVCodec的supported_samplerates可以获取
* @author Liao Qingfu
* @date 2020-04-15
*/#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>#include <libavcodec/avcodec.h>#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>
#include <libavutil/opt.h>/* 检测该编码器是否支持该采样格式 */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{const enum AVSampleFormat *p = codec->sample_fmts;while (*p != AV_SAMPLE_FMT_NONE) { // 通过AV_SAMPLE_FMT_NONE作为结束符if (*p == sample_fmt)return 1;p++;}return 0;
}/* 检测该编码器是否支持该采样率 */
static int check_sample_rate(const AVCodec *codec, const int sample_rate)
{const int *p = codec->supported_samplerates;while (*p != 0) {// 0作为退出条件,比如libfdk-aacenc.c的aac_sample_ratesprintf("%s support %dhz\n", codec->name, *p);if (*p == sample_rate)return 1;p++;}return 0;
}/* 检测该编码器是否支持该通道数, 该函数只是作参考 */
static int check_channel_layout(const AVCodec *codec, const uint64_t channel_layout)
{// 不是每个codec都给出支持的channel_layoutconst uint64_t *p = codec->channel_layouts;if(!p) {printf("the codec %s no set channel_layouts\n", codec->name);return 1;}while (*p != 0) { // 0作为退出条件,比如libfdk-aacenc.c的aac_channel_layoutprintf("%s support channel_layout %d\n", codec->name, *p);if (*p == channel_layout)return 1;p++;}return 0;
}static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
{uint8_t freq_idx = 0; //0: 96000 Hz 3: 48000 Hz 4: 44100 Hzswitch (ctx->sample_rate) {case 96000: freq_idx = 0; break;case 88200: freq_idx = 1; break;case 64000: freq_idx = 2; break;case 48000: freq_idx = 3; break;case 44100: freq_idx = 4; break;case 32000: freq_idx = 5; break;case 24000: freq_idx = 6; break;case 22050: freq_idx = 7; break;case 16000: freq_idx = 8; break;case 12000: freq_idx = 9; break;case 11025: freq_idx = 10; break;case 8000: freq_idx = 11; break;case 7350: freq_idx = 12; break;default: freq_idx = 4; break;}uint8_t chanCfg = ctx->channels;uint32_t frame_length = aac_length + 7;adts_header[0] = 0xFF;adts_header[1] = 0xF1;adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);adts_header[3] = (((chanCfg & 3) << 6) + (frame_length >> 11));adts_header[4] = ((frame_length & 0x7FF) >> 3);adts_header[5] = (((frame_length & 7) << 5) + 0x1F);adts_header[6] = 0xFC;
}
/*
*
*/
static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *output)
{int ret;/* send the frame for encoding */ret = avcodec_send_frame(ctx, frame);if (ret < 0) {fprintf(stderr, "Error sending the frame to the encoder\n");return -1;}/* read all the available output packets (in general there may be any number of them */// 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOFwhile (ret >= 0) {ret = avcodec_receive_packet(ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {fprintf(stderr, "Error encoding audio frame\n");return -1;}size_t len = 0;printf("ctx->flags:0x%x & AV_CODEC_FLAG_GLOBAL_HEADER:0x%x, name:%s\n",ctx->flags, ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER, ctx->codec->name);if((ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER)) {// 需要额外的adts header写入uint8_t aac_header[7];get_adts_header(ctx, aac_header, pkt->size);len = fwrite(aac_header, 1, 7, output);if(len != 7) {fprintf(stderr, "fwrite aac_header failed\n");return -1;}}len = fwrite(pkt->data, 1, pkt->size, output);if(len != pkt->size) {fprintf(stderr, "fwrite aac data failed\n");return -1;}/* 是否需要释放数据? avcodec_receive_packet第一个调用的就是 av_packet_unref* 所以我们不用手动去释放,这里有个问题,不能将pkt直接插入到队列,因为编码器会释放数据* 可以新分配一个pkt, 然后使用av_packet_move_ref转移pkt对应的buffer*/// av_packet_unref(pkt);}return -1;
}/** 这里只支持2通道的转换
*/
void f32le_convert_to_fltp(float *f32le, float *fltp, int nb_samples) {float *fltp_l = fltp; // 左通道float *fltp_r = fltp + nb_samples; // 右通道for(int i = 0; i < nb_samples; i++) {fltp_l[i] = f32le[i*2]; // 0 1 - 2 3fltp_r[i] = f32le[i*2+1]; // 可以尝试注释左声道或者右声道听听声音}
}
/** 提取测试文件:* (1)s16格式:ffmpeg -i buweishui.aac -ar 48000 -ac 2 -f s16le 48000_2_s16le.pcm* (2)flt格式:ffmpeg -i buweishui.aac -ar 48000 -ac 2 -f f32le 48000_2_f32le.pcm* ffmpeg只能提取packed格式的PCM数据,在编码时候如果输入要为fltp则需要进行转换* 测试范例:* (1)48000_2_s16le.pcm libfdk_aac.aac libfdk_aac // 如果编译的时候没有支持fdk aac则提示找不到编码器* (2)48000_2_f32le.pcm aac.aac aac // 我们这里只测试aac编码器,不测试fdkaac
*/
int main(int argc, char **argv)
{char *in_pcm_file = NULL;char *out_aac_file = NULL;FILE *infile = NULL;FILE *outfile = NULL;const AVCodec *codec = NULL;AVCodecContext *codec_ctx= NULL;AVFrame *frame = NULL;AVPacket *pkt = NULL;int ret = 0;int force_codec = 0; // 强制使用指定的编码char *codec_name = NULL;if (argc < 3) {fprintf(stderr, "Usage: %s <input_file out_file [codec_name]>, argc:%d\n",argv[0], argc);return 0;}in_pcm_file = argv[1]; // 输入PCM文件out_aac_file = argv[2]; // 输出的AAC文件enum AVCodecID codec_id = AV_CODEC_ID_AAC;if(4 == argc) {if(strcmp(argv[3], "libfdk_aac") == 0) {force_codec = 1; // 强制使用 libfdk_aaccodec_name = "libfdk_aac";} else if(strcmp(argv[3], "aac") == 0) {force_codec = 1;codec_name = "aac";}}if(force_codec)printf("force codec name: %s\n", codec_name);elseprintf("default codec name: %s\n", "aac");if(force_codec == 0) { // 没有强制设置编码器codec = avcodec_find_encoder(codec_id); // 按ID查找则缺省的aac encode为aacenc.c} else {// 按名字查找指定的encode,对应AVCodec的name字段codec = avcodec_find_encoder_by_name(codec_name);}if (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec context\n");exit(1);}codec_ctx->codec_id = codec_id;codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;codec_ctx->bit_rate = 128*1024;codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;codec_ctx->sample_rate = 48000; //48000;codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);codec_ctx->profile = FF_PROFILE_AAC_LOW; //if(strcmp(codec->name, "aac") == 0) {codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;} else if(strcmp(codec->name, "libfdk_aac") == 0) {codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;} else {codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;}/* 检测支持采样格式支持情况 */if (!check_sample_fmt(codec, codec_ctx->sample_fmt)) {fprintf(stderr, "Encoder does not support sample format %s",av_get_sample_fmt_name(codec_ctx->sample_fmt));exit(1);}if (!check_sample_rate(codec, codec_ctx->sample_rate)) {fprintf(stderr, "Encoder does not support sample rate %d", codec_ctx->sample_rate);exit(1);}if (!check_channel_layout(codec, codec_ctx->channel_layout)) {fprintf(stderr, "Encoder does not support channel layout %lu", codec_ctx->channel_layout);exit(1);}printf("\n\nAudio encode config\n");printf("bit_rate:%ldkbps\n", codec_ctx->bit_rate/1024);printf("sample_rate:%d\n", codec_ctx->sample_rate);printf("sample_fmt:%s\n", av_get_sample_fmt_name(codec_ctx->sample_fmt));printf("channels:%d\n", codec_ctx->channels);// frame_size是在avcodec_open2后进行关联printf("1 frame_size:%d\n", codec_ctx->frame_size);codec_ctx->flags = AV_CODEC_FLAG_GLOBAL_HEADER; //ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里我们强制不带/* 将编码器上下文和编码器进行关联 */if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}printf("2 frame_size:%d\n\n", codec_ctx->frame_size); // 决定每次到底送多少个采样点// 打开输入和输出文件infile = fopen(in_pcm_file, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", in_pcm_file);exit(1);}outfile = fopen(out_aac_file, "wb");if (!outfile) {fprintf(stderr, "Could not open %s\n", out_aac_file);exit(1);}/* packet for holding encoded output */pkt = av_packet_alloc();if (!pkt){fprintf(stderr, "could not allocate the packet\n");exit(1);}/* frame containing input raw audio */frame = av_frame_alloc();if (!frame){fprintf(stderr, "Could not allocate audio frame\n");exit(1);}/* 每次送多少数据给编码器由:* (1)frame_size(每帧单个通道的采样点数);* (2)sample_fmt(采样点格式);* (3)channel_layout(通道布局情况);* 3要素决定*/frame->nb_samples = codec_ctx->frame_size;frame->format = codec_ctx->sample_fmt;frame->channel_layout = codec_ctx->channel_layout;frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);printf("frame nb_samples:%d\n", frame->nb_samples);printf("frame sample_fmt:%d\n", frame->format);printf("frame channel_layout:%lu\n\n", frame->channel_layout);/* 为frame分配buffer */ret = av_frame_get_buffer(frame, 0);if (ret < 0){fprintf(stderr, "Could not allocate audio data buffers\n");exit(1);}// 计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量int frame_bytes = av_get_bytes_per_sample(frame->format) \* frame->channels \* frame->nb_samples;printf("frame_bytes %d\n", frame_bytes);uint8_t *pcm_buf = (uint8_t *)malloc(frame_bytes);if(!pcm_buf) {printf("pcm_buf malloc failed\n");return 1;}uint8_t *pcm_temp_buf = (uint8_t *)malloc(frame_bytes);if(!pcm_temp_buf) {printf("pcm_temp_buf malloc failed\n");return 1;}int64_t pts = 0;printf("start enode\n");for (;;) {memset(pcm_buf, 0, frame_bytes);size_t read_bytes = fread(pcm_buf, 1, frame_bytes, infile);if(read_bytes <= 0) {printf("read file finish\n");break;
// fseek(infile,0,SEEK_SET);
// fflush(outfile);
// continue;}/* 确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份目的是新写入的数据和编码器保存的数据不能产生冲突*/ret = av_frame_make_writable(frame);if(ret != 0)printf("av_frame_make_writable failed, ret = %d\n", ret);if(AV_SAMPLE_FMT_S16 == frame->format) {// 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚ret = av_samples_fill_arrays(frame->data, frame->linesize,pcm_buf, frame->channels,frame->nb_samples, frame->format, 0);} else {// 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚// 将本地的f32le packed模式的数据转为float palanarmemset(pcm_temp_buf, 0, frame_bytes);f32le_convert_to_fltp((float *)pcm_buf, (float *)pcm_temp_buf, frame->nb_samples);ret = av_samples_fill_arrays(frame->data, frame->linesize,pcm_temp_buf, frame->channels,frame->nb_samples, frame->format, 0);}// 设置ptspts += frame->nb_samples;frame->pts = pts; // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率ret = encode(codec_ctx, frame, pkt, outfile);if(ret < 0) {printf("encode failed\n");break;}}/* 冲刷编码器 */encode(codec_ctx, NULL, pkt, outfile);// 关闭文件fclose(infile);fclose(outfile);// 释放内存if(pcm_buf) {free(pcm_buf);}if (pcm_temp_buf) {free(pcm_temp_buf);}av_frame_free(&frame);av_packet_free(&pkt);avcodec_free_context(&codec_ctx);printf("main finish, please enter Enter and exit\n");getchar();return 0;
}
更多资料:https://github.com/0voice
相关文章:
【音视频】音频编码实战
FFmpeg流程 从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。 示例的流程如下所示。 关键函数说明: avcodec_find_encoder:根据指定的AVCodecID查找注册的编码器。avcodec_alloc_context3:为AVCod…...
Git Bash 下使用 SSH 连接出现 “Software caused connection abort” 问题
目录 一、检查网络环境和防火墙设置(失败)二、尝试使用 GitHub 的备用 SSH 端口 443(成功)三、检查 SSH Key 是否被正确加载四、检查是否多个 SSH 进程干扰或者服务异常五、使用 HTTPS 方式临时解决(非 SSH)…...
K8S Pod 常见数据存储方案
假设有如下三个节点的 K8S 集群: k8s31master 是控制节点 k8s31node1、k8s31node2 是工作节点 容器运行时是 containerd 一、理论介绍 1.1、Volumes 卷 Kubernetes 的卷是 pod 的⼀个组成部分,因此像容器⼀样在 pod 的规范(pod.spec&#x…...
JavaScript 模板字符串:更优雅的字符串处理方式
什么是模板字符串? 模板字符串(Template Literals)是 ES6(ES2015)引入的一种新的字符串表示方式,它提供了更强大、更灵活的字符串拼接功能。与传统的字符串使用单引号()或双引号&am…...
DeepSeek智能时空数据分析(五):基于区域人口数量绘制地图散点-大模型搜集数据NL2SQL加工数据
序言:时空数据分析很有用,但是GIS/时空数据库技术门槛太高 时空数据分析在优化业务运营中至关重要,然而,三大挑战仍制约其发展:技术门槛高,需融合GIS理论、SQL开发与时空数据库等多领域知识;空…...
PostSwigger 的 CSRF 漏洞总结
本文所提供的关于 web 安全的相关信息、技术讲解及案例分析等内容,仅用于知识分享与学术交流目的,旨在提升读者对 web 安全领域的认知与理解。以下仅仅是作者对 PostSwigger Web 安全的知识整理和分享,严禁任何非法犯罪活动。 限制 CSRF 的三…...
vue项目页面适配
vue项目页面适配 目的:结合动态设置根字体大小的脚本(如通过 JavaScript 监听屏幕尺寸变化),实现页面元素在不同设备上的自适应缩放 1、安装postcss-pxtorem ### 若项目未集成 postcss,需同步安装: npm …...
AI-Browser适用于 ChatGPT、Gemini、Claude、DeepSeek、Grok的客户端开源应用程序,集成了 Monaco 编辑器。
一、软件介绍 文末提供程序和源码下载学习 AI-Browser适用于 ChatGPT、Gemini、Claude、DeepSeek、Grok、Felo、Cody、JENOVA、Phind、Perplexity、Genspark 和 Google AI Studio 的客户端应用程序,集成了 Monaco 编辑器。使用 Electron 构建的强大桌面应用程序&a…...
Flutter Dart新特性NulI safety late 关键字、空类型声明符?、非空断言!、required 关键字
目录 late 关键字 required关键词: 常用的Model对象使用required Null safety翻译成中文的意思是空安全 null safety 可以帮助开发者避免一些日常开发中很难被发现的错误,并且额外的好处是可以改善性能后的版本都要求使用nul1 safety。Flutter2.2.0(2021年5月19日…...
CF2096G Wonderful Guessing Game 构造
题解 首先考虑没有 ? ? ? 回答的时候,答案是多少。 猜猜需要多少个询问。 ⌈ log 2 n ⌉ ? ⌈ log 3 n ⌉ ? \lceil \log_2n\rceil ? \lceil \log_3n\rceil ? ⌈log2n⌉?⌈log3n⌉? 可以构造一个表,行表示不同的询问,…...
制作一款打飞机游戏26:精灵编辑器
虽然我们基本上已经重建了Axel编辑器,但我不想直接使用它。我想创建一个真正适合我们当前目的的编辑器,那就是编辑精灵(sprites)。这将是今天的一个大目标——创建一个基于模板的编辑器,用它作为我们实际编辑器的起点。…...
深入Java JVM常见问题及解决方案
1. 简介 Java虚拟机(JVM)是Java程序运行的核心环境,但其复杂性可能导致内存泄漏、性能下降、类加载失败等问题。本文从内存管理、垃圾回收、性能调优、异常处理四大方向,结合工具使用与实战案例,详解JVM问题的排查与解…...
【MySQL】Java代码操作MySQL数据库 —— JDBC编程
目录 1. Java的JDBC编程 1.1 Java的数据库编程:JDBC 1.2 JDBC工作原理 1.3 如何在项目中导入数据库驱动包 如何下载数据库驱动包 jar包如何引入项目中 2. 编写JDBC代码 1. 创建并初始化一个数据源(DataSource) 2. 和数据库服务器建立连接 3. 构造 SQL 语句…...
Marmoset Toolbag 5.0 中文汉化版 八猴软件中文汉化版 免费下载
八猴安装包下载链接 https://pan.baidu.com/s/1Mgy3Mrlrb3Tvtc8w7Zn1nA?pwd6666 提取码:6666 Marmoset Toolbag是由Monkey公司推出一款专业动画渲染软件,也叫做八猴渲染器。该软件主要特征功能是可以进行实时模型观察、材质编辑和动画预览,…...
Java编程中常见错误的总结和解决方法
1. 找不到文件 问题描述:尝试编译一个名为ChangeCha.java的文件,但编译器找不到这个文件。错误信息:javac: 找不到文件: ChangeCha.java解决方法:检查文件名是否正确,文件是否存在于当前目录,或者路径是否…...
【GESP】C++三级练习 luogu-B2114 配对碱基链
GESP三级练习,字符串练习(C三级大纲中6号知识点,字符串),难度★✮☆☆☆。 题目题解详见:https://www.coderli.com/gesp-3-luogu-b2114/ 【GESP】C三级练习 luogu-B2114 配对碱基链 | OneCoderGESP三级练…...
C++类设计新思路:封装结构体成员变量
C++类设计新思路:封装结构体成员变量 引言 在C++编程里,类是封装数据和行为的重要手段。常规的类设计直接把成员变量定义在类中,再通过成员函数访问和修改这些变量。不过,有时候我们可以采用不同的设计思路,例如将成员变量封装到结构体里,这样可能会带来一些好处。本文…...
图像预处理-形态学变换
针对二值化图像,其有两个输入,一个输出:输入为原图像、核(结构化元素),输出为形态学变换后的图像。基本操作有腐蚀和膨胀。 一.核 联想到之前的卷积核,也是一种核。 此时的核就跟卷积核不太一…...
关于百度模型迭代个人见解:技术竞速下的应用价值守恒定律
就在前天,在 2025 年 4 月 25 日的百度 Create 开发者大会上,文心大模型 4.5 Turbo 与 X1 Turbo 的发布再次将 AI 行业带入 "涡轮加速" 时代。这两款模型不仅在多模态理解、逻辑推理等核心指标上实现突破,更以80% 的价格降幅重塑行…...
从基础到实战的量化交易全流程学习:1.3 数学与统计学基础——概率与统计基础 | 基础概念
从基础到实战的量化交易全流程学习:1.3 数学与统计学基础——概率与统计基础 | 基础概念 第一部分:概率与统计基础 第1节:基础概念:随机变量、概率分布、大数定律与中心极限定理 一、随机变量与概率分布:用数学描述市场…...
混沌工程领域常用工具的对比分析
以下是混沌工程领域常用工具的对比分析,涵盖主流工具的核心功能、优势、适用场景及局限性,帮助技术团队根据自身需求选择合适的工具: 一、故障注入工具对比 工具核心特点优势适用场景局限性生态集成开源/付费Chaos MonkeyNetflix 开源,随机终止生产环境实例,模拟硬件/进程…...
LINUX的使用(2)- 安装软件
0.防火墙相关 启动防火墙: sudo systemctl start firewalld #查看防火墙列表 firewall-cmd --list-ports 设置防火墙开机自启: sudo systemctl enable firewalld 检查防火墙状态: sudo firewall-cmd --state 允许某个端口(如端…...
一主多从+自组网络,无线模拟量信号传输专治布线PTSD
无线模拟量信号传输模块通过无线方式实现模拟量信号的传输,采集工业现场标准4~20mA电流信号,并将其转换为无线信号进行传输。 以下是关于无线模拟量信号传输模块实现无线模拟量信号传输的详细介绍: 一、模块原理 无线模拟量信号传输模块的…...
IDEA中使用Git
Git工作流程 创建远程仓库 现在我们已经在本地创建了一个Git仓库,但是这只能满足我们单人开发,如果想要团队协作,还需要一个远程仓库 目前比较流行的远程仓库,有下面这两个: github:https://github.com …...
Go RPC 服务方法签名的要求
在 Go 中,RPC 方法的签名有严格的要求,主要是为了保证方法的调用能够通过网络正确地传输和解析。具体要求如下: 1. 方法必须是导出的 RPC 服务的方法必须是导出的(即首字母大写)。这是因为 Go 的反射机制要求服务方法…...
Ant(Ubuntu 18.04.6 LTS)安装笔记
一、前言 本文与【MySQL 8(Ubuntu 18.04.6 LTS)安装笔记】同批次:先搭建数据库,再安装JDK,后面肯定就是部署Web应用。其中Web应用的部署使用 Ant 方式,善始善终,特以笔记。 二、准备 …...
相机DreamCamera2录像模式适配尺寸
在开发中遇到 一个问题,相机切换视频模式时,预览时,界面不能充满屏幕两侧有黑边,客户要求修改,在此记录 一问题现象: 系统相机在视频模式下预览时如下现象如图1,期望现象如图2: 图1 …...
Animate 中HTMLCanvas 画布下实现拖拽、释放、吸附的拼图游戏
1.舞台上物体拖拽 2.松手以后,检查是否移动到范围内,是则自动吸附 3.播放音效 4.变量1,显示在舞台的动态文本中 1.实现拖拽 下面代码实现拖拽和释放 地图模块 //记录原始位置 var OriXthis.my_mc.x; var OriYthis.my_mc.y;this.my_mc.on(&q…...
第十一章-PHP表单传值
第十一章-PHP表单传值 一,核心概念 1. 表单的基本结构(HTML) 通过HTML的<form>标签定义表单,关键属性包括: action: 指定处理表单数据的PHP脚本路径(如action"process.php")…...
互联网大厂Java求职面试:从Java核心到微服务的深度探索
场景引入: 在一个阳光明媚的早上,谢飞机满怀信心地走进了一家知名互联网大厂的面试房间。面试官坐在桌子的另一端,手中拿着一份简历,面带微笑地开始了今天的面试。 第一轮提问:核心语言与平台 面试官: "谢飞机,你好。我看到你熟悉Java SE,能不能简单介绍一下Ja…...
微服务即时通信系统(十二)---入口网关子服务
目录 功能设计 模块划分 业务接口/功能示意图 服务实现流程 网关HTTP接口 网关WebSocket接口 总体流程 服务代码实现 客户端长连接管理封装(connectionManage.hpp) proto文件的编写 身份鉴权proto 事件通知proto 各项请求的URL的确定 服务端完成入口网关服务类…...
ES练习册
es索引结构和数据实例 这里提供索引结构和数据实例提供给大家使用练习,希望大家能够一起成长进步~~~~ #添加索引 PUT /ecommerce_products {"settings": {"number_of_shards": 3,"number_of_replicas": 1,"analysis": {&…...
运算符分为哪几类?哪些运算符常用作判断?简述运算符的优先级
运算符主要分为以下几类: 算术运算符:用于执行基本的数学运算,如加、减、乘、除、取模等。例如:、-、*、/、%。赋值运算符:用于将值赋给变量。例如:、、-、*、/、%。比较运算符ÿ…...
shell编程基础知识及脚本示例
文章目录 前言一、shell变量1.命名规则2.定义及使用变量 二、shell传递参数1.位置参数2. 任意参数 三、shell一维数组0.定义方式1.定义并获取数组的单个元素2.定义并获取数组的所有元素3.定义并获取数组的元素个数4.定义并获取数组的元素索引 四、shell条件判断语法五、shell常…...
再学GPIO(一)
GPIO输出模式 STM32的GPIO(General Purpose Input Output 通用输入输出)引脚支持多种输出模式,不同模式决定了引脚的驱动能力和信号特性。STM32的GPIO输出模式主要分为以下4种: 推挽输出(Push-Pull Output)…...
OpenCV彩色图像分割
OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 灰度图像大多通过算子寻找边缘和区域生长融合来分割图像。彩色图像增加了色彩信息,可以通过不同的色彩值来分割图像,常用彩色空间HSV/HIS、RGB、LAB等都可以用于分割。本节使用inRange…...
django filter 排除字段
在Django中,当你使用filter查询集(QuerySet)时,通常你会根据模型的字段来过滤数据。但是,有时你可能想要排除某些特定的字段,而不是过滤这些字段。这里有几种方法可以实现这一点: 使用exclude方…...
多模态大语言模型arxiv论文略读(四十五)
CAT: Enhancing Multimodal Large Language Model to Answer Questions in Dynamic Audio-Visual Scenarios ➡️ 论文标题:CAT: Enhancing Multimodal Large Language Model to Answer Questions in Dynamic Audio-Visual Scenarios ➡️ 论文作者:Qil…...
Vue3 通过Vue3-Print-Nb在线工单打印 模板打印 自定义打印 打印下载
介绍 通过在应用中集成打印功能,用户可以直接从页面打印工单,不用导出文件或使用其他外部工具。节省时间,提高效率,特别是当需要大量打印时。同时也可以将文件模板上传到数据库,提供给部门工作自行下载。 开源文档&am…...
视觉“解锁”触觉操控:Franka机器人如何玩转刚柔物体?
集智联机器人(Plug & Play Robotics),简称PNP机器人,是Franka Robotics和思灵机器人金牌合作伙伴,集智联机器人团队成员均来自于国内外机器人行业知名企业,具有较强的学术背景。PNP机器人致力于为客户提…...
FlinkUpsertKafka深度解析
1. 设计目标与工作机制 Upsert-Kafka Connector 核心功能:支持以 Upsert(插入/更新/删除) 模式读写 Kafka 数据,适用于需要动态更新结果的场景(如聚合统计、CDC 数据同步)。数据流类型: 作为 …...
百度Create大会深度解读:AI Agent与多模态模型如何重塑未来?
目录 百度Create大会亮点全解析:从数字人到Agent生态布局 数字人商业化:从"拟人"到"高说服力"的进化 Agent生态:从"心响"App看百度的Agent战略布局 "心响"App的技术架构与创新点 多模态大模型&a…...
新能源汽车运动控制器核心芯片选型与优化:MCU、DCDC与CANFD协同设计
摘要:随着新能源汽车产业的迅猛发展,汽车运动控制器的性能和可靠性面临着更高的要求。本文深入探讨了新能源汽车运动控制器中MCU(微控制单元)、DCDC电源管理芯片和CANFD总线通信芯片的选型要点、优化策略及其协同设计方案。通过综…...
【软件工程】 白盒测试简介
1. 前言 在软件测试过程中,白盒测试(White-box Testing)是一种重要方法,它通过检查程序内部结构来验证软件功能。本文以一道典型的伪代码程序为例,结合白盒测试的基本操作,设计语句覆盖测试用例࿰…...
uniapp自定义一个选择年月日时分的组件。
<template><view><u-popup :show"timePopShow" mode"bottom" close"close" open"open" :closeOnClickOverlay"true"><view class"popup-container"><!-- 自定义时间内容 --><vi…...
Git命令(Gitee)
三板斧: git init //初始化本地仓库 git add . //添加所有文件到缓存区 (或指定文件) git commit -m "备注" //提交,填写备注 git remote add origin <远程仓库链接> git push -u origin ma…...
Node.js 应用部署:镜像体积优化与安全的多阶段构建探索
Node.js 应用部署:镜像体积优化与安全的多阶段构建探索 在开发 Node.js 应用时,部署过程中的镜像体积优化和安全性保障是至关重要的环节。本文将通过两种不同的 Docker 部署方式,深入探讨如何实现高效的镜像体积优化和安全的部署环境。 传统的单阶段构建方式 许多开发者在…...
深度解析:Web Crawling与Web Scraping的区别与联系
在现代互联网数据驱动的时代,Web Crawling(网页爬取)和Web Scraping(网页抓取)成为数据采集领域的两大核心技术。尽管两者常被混用,但它们在技术实现、应用目的和操作流程上存在显著差异。本文将基于权威资料,特别是维基百科的定义࿰…...
C# 利用log4net 工作台打印和保存到文件
目录 log4net简介引言1、添加引用库2、添加引用和构建实例3、添加属性配置4、添加配置文件最后 log4net简介 log4net是一个开源的日志记录组件,专为.NET平台设计。它是Apache log4j框架在Microsoft .NET平台的实现,属于Apache Logging Servic…...
2025 VSCode中如何进行dotnet开发环境配置完整教程
我一直都是用Visual Studio 2019来开发C#项目的,用的比较顺手,也习惯了。看其他技术文章有介绍VS Code更轻量,更方便。 所以就想来研究如何使用VS Code,看看它是如何构建代码、调试代码、如何运行C#应用程序。 本文将详细介绍如何…...