FFmpeg合成mp4

本章主要介紹如何使用FFmpeg來將一個音頻文件和一個視頻文件合成一個MP4文件,以及在這個過程中我們如何對編碼過程進行封裝以及sample_rate 重采樣的過程(由于提供的音頻文件的編碼類型為S16,所以我們需要轉化為MP4支持的FLTP浮點類型)。

Muxer

首先我們來介紹如何封裝MP4的封裝器,就是我們將視頻流和音頻流輸入封裝器,封裝器輸出MP4文件。下面是封裝器的頭文件,里面有一些封裝器必要的成員函數。

#ifndef MUXER_H #define MUXER_H #include <iostream> 
// 在C++文件中中導入C庫需要使用extern關鍵字 
extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" 
} class Muxer { public: Muxer(); ~Muxer(); // 初始化 int Init(const char* url); // 釋放資源 void DeInit(); // 將一條視頻/音頻流添加到封裝器 int AddStream(AVCodecContext* codec_ctx); // 將頭發送到封裝器中 int SendHeader(); // 將數據幀發送到封裝器中 int SendPacket(AVPacket* packet); // 將尾發送到封裝器中 int SendTrailer(); // 打開輸入源url int Open(); private: // format上下文 AVFormatContext* fmt_ctx_ = NULL; // 輸入源url,這里可能是url也可以是一個文件路徑 std::string url_ = ""; // 視頻流復用器上下文 AVCodecContext* vid_codec_ctx_ = NULL; AVCodecContext* aud_codec_ctx_ = NULL; // 視頻流 AVStream* vid_st_ = NULL; AVStream* aud_st_ = NULL; // 有沒有對應的流 int video_index_ = -1; int audio_index_ = -1; 
}; 
#endif // MUXER_H

接下來是封裝器的具體時間,我們暫時只實現最基礎的功能:

int Muxer::Init(const char *url) { int ret = avformat_alloc_output_context2(&fmt_ctx_, NULL, NULL,url); if(ret < 0) { char errbuf[1024] = {0}; av_strerror(ret, errbuf, sizeof(errbuf) - 1);printf("avformat_alloc_output_context2 failed:%s\n", errbuf); return -1; } url_ = url; return 0; 
}

由于這是第一個具體的函數實現,所以我就放上了獲取錯誤的函數,后面我就不說了。avformat_alloc_output_context2用來初始化輸出格式上下文。最后是將傳入的url參數賦值給類成員。

void Muxer::DeInit() { if(fmt_ctx_) { avformat_close_input(&fmt_ctx_); } url_ = ""; aud_codec_ctx_ = NULL; aud_stream_ = NULL; audio_index_ = -1; vid_codec_ctx_ = NULL; vid_stream_ = NULL; video_index_ = -1; 
}

這里主要的功能就是關閉輸出格式上下文,然后將其他的類成員設置為初始狀態。

int Muxer::AddStream(AVCodecContext *codec_ctx) { if(!fmt_ctx_) { printf("fmt ctx is NULL\n"); return -1; } if(!codec_ctx) { printf("codec ctx is NULL\n"); return -1; } AVStream *st = avformat_new_stream(fmt_ctx_, NULL); if(!st) { printf("avformat_new_stream failed\n"); return -1; } // st->codecpar->codec_tag = 0; // 從編碼器上下文復制 avcodec_parameters_from_context(st->codecpar, codec_ctx);av_dump_format(fmt_ctx_, 0, url_.c_str(), 1); // 判斷當前的是視頻流還是音頻流 if(codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { aud_codec_ctx_ = codec_ctx; aud_stream_ = st; audio_index_ = st->index; } else if(codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { vid_codec_ctx_ = codec_ctx; vid_stream_ = st; video_index_ = st->index; } return 0; 
}
  • avformat_new_stream的第二個參數通常是NULL,自動分配流。但是如果是已知編碼器,可以直接傳入AVCodec*。
  • 在判斷當前傳入的流的種類后,初始化對應的類成員。
int Muxer::SendHeader()
{if(!fmt_ctx_) {printf("fmt ctx is NULL\n");return -1;}/** 這里其實可以選擇封裝參數(如mp4的faststart)* AVDictionary* option = NULL;* av_dict_set(&options, "movflags", "faststart", 0);*/int ret = avformat_write_header(fmt_ctx_, NULL);if (ret != 0) {char errbuf[1024] = {0};av_strerror(ret, errbuf, sizeof(errbuf) - 1);printf("avformat_write_header failed:%s\n", errbuf);return -1;}return 0;
}
  • 需要注意的是這個函數必須在所有流添加完成后調用,因為avformat_write_header必須在所有流都添加完畢后調用.
  • 如果后續還要修改參數,需要在調用前完成。
int Muxer::SendPacket(AVPacket *packet)
{int stream_index = packet->stream_index;if (!packet || packet->size <=0 || packet->data) {printf("packet is null\n");if (packet) {av_packet_free(&packet);}return -1;}AVRational src_time_base; // 編碼后的包AVRational dst_time_base; // mp4輸出文件對應流的time_baseif (vid_st_ && vid_codec_ctx_ && stream_index == video_index_) {src_time_base = vid_codec_ctx_->time_base;dst_time_base = vid_st_->time_base;}else if (aud_st_ && aud_codec_ctx_ && stream_index == audio_index_) {src_time_base = aud_codec_ctx_->time_base;dst_time_base = aud_st_->time_base;}packet->pts = av_rescale_q(packet->pts, src_time_base, dst_time_base);packet->dts = av_rescale_q(packet->dts, src_time_base, dst_time_base);packet->duration = av_rescale_q(packet->duration, src_time_base, dst_time_base);int ret = 0;ret = av_interleaved_write_frame(fmt_ctx_, packet);// ret = av_write_frame(fmt_ctx_, packet);av_packet_free(&packet);if (ret == 0) {return 0;}else {char errbuf[1024] = {0};av_strerror(ret, errbuf, sizeof(errbuf) - 1);printf("avformat_write_header failed:%s\n", errbuf);return -1;}
}
  • av_interleaved_write_frame和av_write_frame的功能其實差不多,不過前者會有一些緩存,而后者是直接寫入到文件。前者的緩存目的是根據pts對幀進行排序。
  • 這里比較重要的就是時間基的轉化問題。為什么要進行時間基轉化呢:不同的音視頻流都有自己的時間基,也就是fps,但是當我們合成的時候,就要統一這些時間基,把他們統一到新編碼格式上。
int Muxer::SendTrailer()
{if(!fmt_ctx_) {printf("fmt ctx is NULL\n");return -1;}// 寫入尾部信息int ret = av_write_trailer(fmt_ctx_);if (ret != 0) {char errbuf[1024] = {0};av_strerror(ret, errbuf, sizeof(errbuf) - 1);printf("av_write_trailer failed:%s\n", errbuf);return -1;}return 0;
}
  • 這里的主要函數是av_write_trailer,它做了以下幾件事:
    • 寫入文件尾部信息(如MP4,MKV中的索引表);
    • 刷新內部緩沖區;
    • 調用每個AVStream的codec相關清理代碼;
    • 確保生成的文件可被播放器正確讀取;
    • 釋放部分資源(這里還需要手動關閉avio_close()和avformat_free_context())

AudioEncoder

接下來是音頻編碼器,用來編碼輸入的音頻流數據。

#ifndef AUDIOENCODER_H
#define AUDIOENCODER_Hextern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
class AudioEncoder
{
public:AudioEncoder();~AudioEncoder();// 這里使用的AAC音頻流,如果要適配更多的流,可以自己添加int InitAAC(int channels, int sample_rate, int bit_rate);
//    int InitMP3(/*int channels, int sample_rate, int bit_rate*/);void DeInit();  // 釋放資源AVPacket *Encode(AVFrame *farme, int stream_index, int64_t pts, int64_t time_base);int GetFrameSize(); // 獲取一幀數據 每個通道需要多少個采樣點int GetSampleFormat();  // 編碼器需要的采樣格式AVCodecContext *GetCodecContext();int GetChannels();int GetSampleRate();
private:// 默認值int channels_ = 2; // 雙聲道int sample_rate_ = 44100; // 采樣率int bit_rate_ = 128*1024; // 比特率int64_t pts_ = 0; // 顯示時間:顯示的時間  dts是解碼時間:開始解碼當前幀的時間AVCodecContext * codec_ctx_ = NULL;
};#endif // AUDIOENCODER_H

這邊的音頻編碼器只封裝了AAC的音頻流,并且設置了一些原始數據,后面可以再拓展。

int AudioEncoder::InitAAC(int channels, int sample_rate, int bit_rate)
{// 初始化當前參數channels_ = channels;sample_rate_ = sample_rate;bit_rate_ = bit_rate;// 根據ID尋找編碼器AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);if(!codec) {printf("avcodec_find_encoder AV_CODEC_ID_AAC failed\n");return -1;}// 為編碼器分配上下文codec_ctx_ = avcodec_alloc_context3(codec);if(!codec_ctx_) {printf("avcodec_alloc_context3 AV_CODEC_ID_AAC failed\n");return -1;}// 配置編碼器上下文參數codec_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 取消AAC的adts頭codec_ctx_->sample_rate = sample_rate;codec_ctx_->bit_rate = bit_rate;// 這是新的寫法,這個函數會配置nb_channels和channel_layoutav_channel_layout_default(&codec_ctx_->ch_layout, channels);// 編碼采樣格式codec_ctx_->sample_fmt = AV_SAMPLE_FMT_FLTP; // 平面浮點數int ret = avcodec_open2(codec_ctx_, NULL, NULL);if (ret != 0) {char errbuf[1024] = {0};av_strerror(ret, errbuf, sizeof(errbuf) - 1);printf("avcodec_open2 failed:%s\n", errbuf);return -1;}printf("InitAAC success\n");return 0;
  • 這里需要注意的是av_channel_layout_default,這是新的寫法,之前需要單獨分來對channel相關變量進行賦值。
// 這就是編碼函數了
AVPacket *AudioEncoder::Encode(AVFrame *frame, int stream_index, int64_t pts, int64_t time_base)
{if (!codec_ctx_) {printf("codec_ctx_ null\n");return NULL;}// 時間基轉換pts = av_rescale_q(pts, AVRational{1, (int)time_base}, codec_ctx_->time_base);if (frame) {frame->pts = pts;}int ret = avcodec_send_frame(codec_ctx_, frame);if (ret != 0) {char errbuf[1024] = {0};av_strerror(ret, errbuf, sizeof (errbuf) - 1);printf("avcodec_send_frame failed:%s\n", errbuf);return NULL;}AVPacket*  packet = av_packet_alloc();ret = avcodec_receive_packet(codec_ctx_, packet);if (ret != 0) {char errbuf[1024] = {0};av_strerror(ret, errbuf, sizeof (errbuf) - 1);printf("avcodec_send_frame failed:%s\n", errbuf);return NULL;}packet->stream_index = stream_index;return packet;
}
  • 設置好編碼器參數后就是編碼了,將數據幀一個一個編碼為packet,最后記得設置一下index返回
  • 每一條音頻和視頻都是分開的,有自己的index(編號)。

Main

主函數的內容還是比較多的,由于涉及到一些常規的操作,比如打開文件等,這里就不都解釋了,我們主要看一些比較重要的需要記錄的地方。

  • 首先來看一些宏定義,他們定義了我們轉化視頻的一些參數。
// 視頻的寬和高
#define YUV_WIDTH 720
#define YUV_HEIGHT 576
#define YUV_FPS 25
// 比特率
#define VIDEO_BIT_RATE 512*1024
// 采樣率
#define PCM_SAMPLE_RATE 44100
#define PCM_CHANNELS 2#define AUDIO_BIT_RATE 128*1024// 基準時間 本例子中的時間是5s 也就是下面的時間*5
#define AUDIO_TIME_BASE 1000000
#define VIDEO_TIME_BASE 1000000
  • 接著這里有一個計算YUV420P編碼格式幀大小的地方。
  int y_frame_size = yuv_width * yuv_height;int u_frame_size = yuv_width * yuv_height / 4;int v_frame_size = yuv_width * yuv_height / 4;int yuv_frame_size = y_frame_size * u_frame_size * v_frame_size;
/*
* 可以看到這里YUV三個方向的size計算方式不同
# 這是因為在YUV420中,UV方向的比特率都是Y方向的1/4
*/
  • 最后看一下主循環
while(1) {if (audio_finish && video_finish)break;printf("apts:%0.0lf, vpts:%0.0lf\n", audio_pts/1000, video_pts/1000);if ((video_finish != 1 && audio_pts > video_pts)|| (video_finish != 1 && audio_finish ==1)) {read_len = fread(yuv_frame_buf, 1, yuv_frame_size, in_yuv_fd);// 文件中的視頻幀內容已經消耗完if (read_len < yuv_frame_size) {video_finish = 1;printf("fread yuv_frame_buf finish\n");}if (video_finish != 1) {packet = video_encoder.Encode(yuv_frame_buf, yuv_frame_size, video_index,video_pts, video_time_base);}else {// 這里有一個沖刷編碼器的過程packet = video_encoder.Encode(NULL, 0, video_index,video_pts, video_time_base);}// 疊加ptsvideo_pts += video_frame_duration; // 疊加ptsif (packet) {mp4_muxer.SendPacket(packet);}}else if (audio_finish != 1) {read_len = fread(pcm_frame_buf, 1, pcm_frame_size, in_pcm_fd);if (read_len < pcm_frame_size) {audio_finish = 1;printf("fread pcm_frame_buf finish\n");}if (audio_finish != 1) {AVFrame* fltp_frame = AllocFltpPcmFrame(pcm_channels, audio_encoder.GetFrameSize());ret = audio_resampler.ResampleFromS16ToFLTP(pcm_frame_buf, fltp_frame);packet = audio_encoder.Encode(fltp_frame, audio_index,audio_pts, audio_time_base);FreePcmFrame(fltp_frame);}else {packet = audio_encoder.Encode(NULL, audio_index,audio_pts, audio_time_base);}audio_pts += audio_frame_duration;if (packet) {mp4_muxer.SendPacket(packet);}}}

下面是主函數的本體:

#include <iostream>
#include "audioencoder.h"
#include "videoencoder.h"
#include "muxer.h"
#include "audioresampler.h"using namespace std;#define YUV_WIDTH 720
#define YUV_HEIGHT 576
#define YUV_FPS 25#define VIDEO_BIT_RATE 512*1024#define PCM_SAMPLE_RATE 44100
#define PCM_CHANNELS 2#define AUDIO_BIT_RATE 128*1024// 基準時間 本例子中的時間是5s 也就是下面的時間*5
#define AUDIO_TIME_BASE 1000000
#define VIDEO_TIME_BASE 1000000int main(int argc, char* argv[])
{if (argc != 4) {printf("usage -> exe in.yuv in.pcm out.mp4");return -1;}const char* in_yuv_name = argv[1];const char* in_pcm_name = argv[2];const char* out_mp4_name = argv[3];FILE* in_yuv_fd = NULL;FILE* in_pcm_fd = NULL;in_yuv_fd = fopen(in_yuv_name, "rb");if (!in_yuv_fd) {printf("Failed to open %s file\n", in_yuv_fd);return -1;}in_pcm_fd = fopen(in_pcm_name, "rb");if (!in_pcm_fd) {printf("Failed to open %s file\n", in_pcm_fd);return -1;}int ret = 0;// 初始化編碼器,包括視頻,音頻編碼器int yuv_width = YUV_WIDTH;int yuv_height = YUV_HEIGHT;int yuv_fps = YUV_FPS;int video_bit_rate = VIDEO_BIT_RATE;VideoEncoder video_encoder;ret = video_encoder.InitH264(yuv_width, yuv_height, yuv_fps, video_bit_rate);if (ret < 0) {printf("video_encoder.InitH264 failed\n");return -1;}int y_frame_size = yuv_width * yuv_height;int u_frame_size = yuv_width * yuv_height / 4;int v_frame_size = yuv_width * yuv_height / 4;int yuv_frame_size = y_frame_size * u_frame_size * v_frame_size;uint8_t* yuv_frame_buf = (uint8_t*)malloc(yuv_frame_size);if (!yuv_frame_buf) {printf("malloc(yuv_frame_size\n");return -1;}int pcm_channels = PCM_CHANNELS;int pcm_sample_rate = PCM_SAMPLE_RATE;int pcm_sample_format = AV_SAMPLE_FMT_FLTP;int audio_bit_rate = AUDIO_BIT_RATE;int pcm_frame_size = av_get_bytes_per_sample((AVSampleFormat)pcm_sample_format);AudioEncoder audio_encoder;ret = audio_encoder.InitAAC(pcm_channels, pcm_sample_rate, audio_bit_rate);if (ret < 0) {printf("audio_encoder.InitAAC failed\n");return -1;}uint8_t* pcm_frame_buf = (uint8_t*)malloc(pcm_frame_size);// 這里需要進行一下重采樣 將 S16 轉化為 FLTPAudioResampler audio_resampler;ret = audio_resampler.InitFromS16ToFLTP(pcm_channels, pcm_sample_rate,audio_encoder.GetChannels(), audio_encoder.GetSampleFormat());if (ret < 0) {printf("audio_resampler.InitFromS16ToFLTP failed\n");return -1;}Muxer mp4_muxer;ret = mp4_muxer.Init(out_mp4_name);if (ret < 0) {printf("mp4_muxer.Init failed\n");return -1;}// 將流添加到封裝器中ret = mp4_muxer.AddStream(video_encoder.GetCodecContext());if (ret < 0) {printf("mp4_muxer.AddStream video failed\n");return -1;}ret = mp4_muxer.AddStream(audio_encoder.GetCodecContext());if (ret < 0) {printf("mp4_muxer.AddStream video failed\n");return -1;}ret = mp4_muxer.Open();if (ret < 0) {return -1;}ret = mp4_muxer.SendHeader();if (ret < 0) {return -1;}int64_t audio_time_base = AUDIO_TIME_BASE;int64_t video_time_base = VIDEO_TIME_BASE;double audio_pts = 0;double video_pts = 0;double audio_frame_duration = 1.0 * audio_encoder.GetFrameSize()/pcm_sample_rate*audio_time_base;double video_frame_duration = 1.0/yuv_fps * video_time_base;int audio_finish = 0;int video_finish = 0;size_t read_len = 0;AVPacket* packet = NULL;int audio_index = mp4_muxer.GetAudioStreamIndex();int video_index = mp4_muxer.GetVideoStreamIndex();while(1) {if (audio_finish && video_finish)break;printf("apts:%0.0lf, vpts:%0.0lf\n", audio_pts/1000, video_pts/1000);if ((video_finish != 1 && audio_pts > video_pts)|| (video_finish != 1 && audio_finish ==1)) {read_len = fread(yuv_frame_buf, 1, yuv_frame_size, in_yuv_fd);if (read_len < yuv_frame_size) {video_finish = 1;printf("fread yuv_frame_buf finish\n");}if (video_finish != 1) {packet = video_encoder.Encode(yuv_frame_buf, yuv_frame_size, video_index,video_pts, video_time_base);}else {packet = video_encoder.Encode(NULL, 0, video_index,video_pts, video_time_base);}video_pts += video_frame_duration; // 疊加ptsif (packet) {mp4_muxer.SendPacket(packet);}}else if (audio_finish != 1) {read_len = fread(pcm_frame_buf, 1, pcm_frame_size, in_pcm_fd);if (read_len < pcm_frame_size) {audio_finish = 1;printf("fread pcm_frame_buf finish\n");}if (audio_finish != 1) {AVFrame* fltp_frame = AllocFltpPcmFrame(pcm_channels, audio_encoder.GetFrameSize());ret = audio_resampler.ResampleFromS16ToFLTP(pcm_frame_buf, fltp_frame);packet = audio_encoder.Encode(fltp_frame, audio_index,audio_pts, audio_time_base);FreePcmFrame(fltp_frame);}else {packet = audio_encoder.Encode(NULL, audio_index,audio_pts, audio_time_base);}audio_pts += audio_frame_duration;if (packet) {mp4_muxer.SendPacket(packet);}}}ret = mp4_muxer.SendTrailer();if (ret < 0) {printf("mp4_muxer.SendTrailer failed\n");}printf("write mp4 finish\n");if (yuv_frame_buf)free(yuv_frame_buf);if (pcm_frame_buf)free(pcm_frame_buf);if (in_yuv_fd)fclose(in_yuv_fd);if (in_pcm_fd)fclose(in_pcm_fd);return 0;
}

參考資料:https://github.com/0voice

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98993.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98993.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98993.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第十九章 使用LAMP架構部署動態網站環境

第十九章 使用LAMP架構部署動態網站環境 文章目錄第十九章 使用LAMP架構部署動態網站環境一、安裝Httpd服務1、安裝httpd服務2、啟動httpd服務3、設置允許通過防火墻4、驗證http服務是否成功二、安裝Mariadb服務1、安裝Mariadb服務2、啟動Mariadb服務三、安裝PHP服務1、列出可用…

Selenium應用中的核心JavaScript操作技巧

Selenium是一款強大的瀏覽器自動化測試工具&#xff0c;其操作瀏覽器的能力部分來自于其內嵌的JavaScript執行引擎。這使得Selenium不僅能夠模擬用戶在瀏覽器中的各種操作&#xff0c;還能執行復雜的JavaScript腳本&#xff0c;以實現更為精細的控制。本文將探討如何通過Seleni…

《Linux 基礎指令實戰:新手入門的命令行操作核心教程(第一篇)》

前引&#xff1a;當你第一次面對 Linux 系統中那片閃爍著光標、只有黑白字符的終端界面時&#xff0c;或許會和很多初學者一樣感到些許茫然&#xff1a;這些由字母和符號組成的 “指令” 究竟該如何輸入&#xff1f;它們又能完成哪些神奇的操作&#xff1f;其實&#xff0c;Lin…

03.【Linux系統編程】基礎開發工具1(yum軟件安裝、vim編輯器、編輯器gcc/g++)

目錄 1. 軟件包管理器 1.1 什么是軟件包 1.2 Linux軟件生態 1.3 yum具體操作 1.3.1 查看軟件包 1.3.2 安裝軟件 1.3.3 卸載軟件 1.3.4 注意事項(測試網絡) 1.3.5 yum指令集總結 1.4 yum源目錄、安裝源 2. Vim編輯器的使用 2.1 Linux編輯器-vim使用 2.2 vim的基本概…

3DMAX自動材質開關插件AutoMaterial安裝和使用方法

3DMAX自動材質開關AutoMaterial&#xff0c;是一個3dMax腳本插件&#xff0c;它根據材質編輯器中當前活動的材質自動將材質應用于3dMax中新創建的對象&#xff0c;也適用于您復制的沒有材質的對象。它作為一個開關&#xff0c;可以綁定到按鈕或菜單來打開和關閉它。該工具的創建…

Linux內核調優實戰指南

內核調優通常通過修改內核運行時參數來實現&#xff0c;這些參數的配置文件是 Linux 系統中核心的性能調整點。 內核調優配置文件名稱 /etc/sysctl.conf: 這是最傳統和主要的內核參數配置文件。系統啟動時或手動執行 sysctl -p 命令時會讀取并應用其中的設置。/etc/sysctl.d/*.…

Java基礎常見知識點

Java 中 和 equals() 的區別詳解_java中與equals的區別及理解-CSDN博客https://blog.csdn.net/m0_64432106/article/details/142026852深入理解Java中方法的參數傳遞機制 - 悟小天 - 博客園https://www.cnblogs.com/sum-41/p/10799555.html浮點型精度是什么意思&#xff1f;為…

OD C卷 -【高效貨運】

文章目錄高效貨運高效貨運 貨車的額定載貨量為wt&#xff1b;貨物A單件重量為wa&#xff0c;單件運費利潤為pa;貨物B單件重量wb&#xff0c;單件運費利潤為pb;每次出車必須包含A、B貨物&#xff0c;且單件貨物都不可分割&#xff0c;總重量達到額定的載貨量wt;每次出車能夠獲取…

手動解壓并讀取geo 文件 series_matrix_table_begin series_matrix_table_end之間的數據

手動解壓并讀取geo 文件 series_matrix_table_begin series_matrix_table_end之間的數據 1. 手動解壓并讀取文件內容 file_path <- “K:/download/geo/raw_data/GEO/GSE32967_series_matrix.txt.gz” 使用latin1編碼讀取文件所有行 con <- gzfile(file_path, “r”) all_…

主板硬件研發基礎--DP/DP++

現在的主板大多數使用的是比DP功能更加強大的DP++。 DisplayPort++(DP++)是 DisplayPort 技術的增強版,旨在提升與多種視頻接口的兼容性和連接性能。以下是關于它的詳細介紹: 功能特性 多協議兼容:DP++ 接口不僅支持 DisplayPort 標準的信號傳輸,還可以通過內部的轉換電…

科技行業新聞發布平臺哪家好?多場景推廣專業方案服務商推薦

面對海量得新聞發布平臺和碎片化的傳播場景&#xff0c;如何精準選擇推廣方案無疑是企業主面臨的一大難題&#xff0c;對于技術迭代迅速的科技行業更是如此。針對復雜的市場環境&#xff0c;一些專業的新聞發布平臺往往能夠針對性地給出營銷方案&#xff0c;并提供一定技術支持…

SystemVerilog 學習之SystemVerilog簡介

SystemVerilog簡介SystemVerilog是一種硬件描述和驗證語言&#xff08;HDVL&#xff09;&#xff0c;由Accellera開發并于2005年成為IEEE標準&#xff08;IEEE 1800&#xff09;。它在傳統Verilog基礎上擴展了高級驗證和設計功能&#xff0c;廣泛應用于數字電路設計、驗證及系統…

JavaWeb--day3--AjaxElement路由打包部署

&#xff08;以下內容全部來自上述課程及課件&#xff09; Ajax &#xff08;此章節純粹演示&#xff0c;因服務器端url鏈接失效&#xff0c;所以無法實戰&#xff09; 1. 同步與異步 同步&#xff1a; 瀏覽器頁面在發送請求給服務器&#xff0c;在服務器處理請求的過程…

IMF GDP的bug

IMF GDP 數據底子是官方數字&#xff0c;基本是沿用官方的&#xff0c;雖然經過修訂或估算&#xff0c;存在4大“bug”&#xff1a;1. 依賴官方上報&#xff0c;真實性不保證2. PPP GDP 虛高&#xff0c;居民實際消費力低很多ppp gdp高&#xff0c;甚至gdp高的地方&#xff0c;…

第2篇:數據持久化實戰

在上一篇中&#xff0c;我們構建了一個基于內存存儲的食譜助手。說實話&#xff0c;內存存儲雖然簡單&#xff0c;但有個致命問題&#xff1a;程序一重啟&#xff0c;數據就全沒了。 所以這篇我們要解決數據持久化的問題&#xff0c;將食譜助手從內存存儲升級到SQLite數據庫。 …

Java推薦系統與機器學習實戰案例

基于Java的推薦系統與機器學習實例 以下是一些基于Java的推薦系統與機器學習實例的參考方向及開源項目,涵蓋協同過濾、矩陣分解、深度學習等常見方法。內容根據實際項目和技術文檔整理,可直接用于學習或開發。 協同過濾實現 用戶-物品評分預測 使用Apache Mahout的基于用戶…

AI生成內容檢測的綜合方法論與技術路徑

一、AI內容檢測技術的分類與原理當前AI內容檢測技術主要分為四大類&#xff0c;每類都有其獨特的原理和應用場景&#xff1a;1. 基于語言特征分析的檢測方法這類方法通過挖掘人類寫作與AI生成文本之間的統計學差異進行判斷&#xff1a;1.1 詞匯使用模式分析AI生成的文本在詞匯選…

可可圖片編輯 HarmonyOS(5)濾鏡效果

可可圖片編輯 HarmonyOS&#xff08;5&#xff09;濾鏡效果 前言 可可圖片編輯也實現了濾鏡效果&#xff0c;主要是利用 Image組件的 colorFilter 屬性實現。濾鏡的關鍵屬性 colorFilter colorFilter 的主要作用是給圖像設置顏色濾鏡效果。 其核心原理是使用一個 4x5 的顏色矩陣…

< JS事件循環系列【二】> 微任務深度解析:從本質到實戰避坑

在上一篇關于 JS 事件循環的文章中&#xff0c;我們提到 “微任務優先級高于宏任務” 這一核心結論&#xff0c;但對于微任務本身的細節并未展開。作為事件循環中 “優先級最高的異步任務”&#xff0c;微任務的執行機制直接影響代碼邏輯的正確性&#xff0c;比如Promise.then的…

STM32 單片機開發 - SPI 總線

一、SPI 總線概念SPI 總線 --- Serial Peripheral Interface&#xff0c;即串行外設接口SPI 是摩托羅拉公司設計的一款 串行、同步、全雙工總線&#xff1b;SPI 總線是三線 / 四線制總線&#xff0c;分別是&#xff1a;SPI_SCK&#xff08;時鐘線&#xff09;、S…