FFmpeg/SDL相關(guān)

FFMpeg 實(shí)現(xiàn)視頻編碼、解碼、封裝、解封裝、轉(zhuǎn)碼、縮放

FFmpeg音視頻解碼過(guò)程

  1. 初始化網(wǎng)絡(luò) avformat_network_init
  2. 打開(kāi)文件avformat_open_input
  3. 從文件中提取流信息avformat_find_stream_info
  4. 在多個(gè)數(shù)據(jù)流中找到視頻流(類型為MEDIA_TYPE_VIDEO)和音頻流(AVMEDIA_TYPE_AUDIO
  5. 查找相對(duì)應(yīng)的解碼器avcodec_find_decoder
  6. 打開(kāi)解碼器avcodec_open2
  7. 從數(shù)據(jù)流中讀取數(shù)據(jù)到Packet中av_read_frame
  8. 將數(shù)據(jù)送入到解碼器內(nèi)部avcodec_send_packet
  9. 為解碼幀分配內(nèi)存av_frame_alloc
  10. 接收解碼幀avcodec_receive_frame
  11. 解碼完成后需要關(guān)閉avformat_open_input打開(kāi)的輸入流,avcodec_open2打開(kāi)的CODEC
    image.png

    注:注冊(cè)所支持的文件格式和遍解碼器CODECav_register_all接口已經(jīng)遺棄,不再需要調(diào)用這個(gè)接口注冊(cè)了。參考:ffmpeg4.2.2 av_register_all()的分析

打開(kāi)一個(gè)視頻解碼器示例

// 獲取視頻流
videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
// 獲取codec
AVCodec *vcodec = avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id);
// 構(gòu)造AVCodecContext ,并將vcodec填入AVCodecContext中
AVCodecContext *vc = avcodec_alloc_context3(vcodec); 
// 初始化AVCodecContext
avcodec_parameters_to_context(vc, ic->streams[videoStream]->codecpar); 
// 打開(kāi)解碼器,由于之前調(diào)用avcodec_alloc_context3(vcodec)初始化了vc,
// 那么codec(第2個(gè)參數(shù))可以填NULL
int ret = avcodec_open2(vc, NULL,NULL); 

FFmpeg重要結(jié)構(gòu)體

參考:FFMPEG中最關(guān)鍵的結(jié)構(gòu)體之間的關(guān)系

AVCodecContext

音視頻編解碼器上下文。作為AVStream的一個(gè)字段存在。avcodec_open2、avcodec_send_packet、avcodec_receive_frame打開(kāi)編解碼器和解碼時(shí)依賴。在使用avcodec_open2打開(kāi)后,需要使用avcodec_close將其關(guān)閉。

AVCodecContext* avcodecCtx = avformatCtx->streams[streamId]->codec;

AVCodec

AVCodec是存儲(chǔ)編解碼器信息的結(jié)構(gòu)體,表示音視頻編解碼器,著重于功能函數(shù)。
參考: AVCodecContext和AVCodec

AVFormatContext

在FFmpeg中有很重要的作用,描述一個(gè)多媒體文件的構(gòu)成及其基本信息,存放了視頻編解碼過(guò)程中的大部分信息。通常該結(jié)構(gòu)體由avformat_open_input分配存儲(chǔ)空間,在最后調(diào)用avformat_input_close關(guān)閉。

AVStream

AVStream表示AVFormatContext中一條具體的流結(jié)構(gòu),比如:音頻流、視頻流和字幕流等。在解碼的過(guò)程中,作為AVFormatContext的一個(gè)字段存在,不需要單獨(dú)的處理。

AVStream* avstream = avformatCtx->streams[streamId];

AVpacket

用來(lái)存放解碼之前的數(shù)據(jù),它只是一個(gè)容器,其data成員指向?qū)嶋H的數(shù)據(jù)緩沖區(qū),在解碼的過(guò)程中可由av_read_frame創(chuàng)建和填充AVPacket中的數(shù)據(jù)緩沖區(qū),當(dāng)數(shù)據(jù)緩沖區(qū)不再使用的時(shí)候可以調(diào)用av_free_apcket釋放這塊緩沖區(qū),最新接口改為av_packet_unref釋放。
對(duì)于視頻(Video)來(lái)說(shuō),AVPacket通常包含一個(gè)壓縮的Frame,而音頻(Audio)則有可能包含多個(gè)壓縮的Frame。并且,一個(gè)Packet有可能是空的,不包含任何壓縮數(shù)據(jù),只含有side data(side data,容器提供的關(guān)于Packet的一些附加信息。例如,在編碼結(jié)束的時(shí)候更新一些流的參數(shù))。
AVPacket結(jié)構(gòu)定義如下:

typedef struct AVPacket {
    // 若為空,則表示AVPacket未使用引用計(jì)數(shù)管理負(fù)載數(shù)據(jù),否則指向存儲(chǔ)負(fù)載數(shù)據(jù)的引用計(jì)數(shù)AVBufferRef -> AVBuffer
    AVBufferRef *buf;
    
    // 基于AVStream->time_base的pts,若文件中沒(méi)有,則為AV_NOPTS_VALUE。
    // pts必須大于等于dts,因?yàn)轱@示不能早于解碼
    int64_t pts;
    
    // 基于AVStream->time_base的dts,若文件中沒(méi)有,則為AV_NOPTS_VALUE。
    int64_t dts;
    // 負(fù)載數(shù)據(jù)
    uint8_t *data;
    // 負(fù)載數(shù)據(jù)的長(zhǎng)度
    int   size;
    // 屬于AVFormatContext中的哪個(gè)AVStream
    int   stream_index;

    // AV_PKT_FLAG_KEY表示關(guān)鍵幀
    int   flags;

    // 攜帶的不同類型的side data
    AVPacketSideData *side_data;
    int side_data_elems;

    // 當(dāng)前幀的持續(xù)時(shí)間,基于AVStream->time_base
    int64_t duration;
    // byte position in stream, -1 if unknown
    int64_t pos;                            
} AVPacket;

參考:
FFmpeg數(shù)據(jù)結(jié)構(gòu):AVPacket解析
FFmpeg之AVPacket

AVFrame

typedef struct AVFrame {

     //指向圖片/頻道平面的指針
    uint8_t *data[AV_NUM_DATA_POINTERS];

    //對(duì)于視頻,每行圖片的大小以字節(jié)為單位。
    //對(duì)于音頻,每個(gè)平面的大?。ㄒ宰止?jié)為單位)。
    int linesize[AV_NUM_DATA_POINTERS];
    .....

    //此幀描述的音頻采樣數(shù)(每個(gè)通道)
    int nb_samples;

    // 音頻數(shù)據(jù)的采樣率
    int sample_rate;
    .....
} AVFrame;

存放從AVPacket中解碼出來(lái)的原始數(shù)據(jù),其必須通過(guò)av_frame_alloc來(lái)創(chuàng)建,通過(guò)av_frame_free來(lái)釋放。

AVFrame *rgbFrame = av_frame_alloc();

AVPacket類似,AVFrame中也有一塊數(shù)據(jù)緩存空間data,在調(diào)用av_frame_alloc的時(shí)候并不會(huì)為這塊緩存區(qū)域分配空間,需要使用av_image_alloc來(lái)為其申請(qǐng)空間。

av_image_alloc(rgbFrame->data, rgbFrame->linesize, videoWidth, videoHeight, AV_PIX_FMT_RGB32, 1);

在解碼的過(guò)程使用了兩個(gè)AVFrame,這兩個(gè)AVFrame分配緩存空間的方法也不相同:
一個(gè)AVFrame用來(lái)存放從AVPacket中解碼出來(lái)的原始數(shù)據(jù),這個(gè)AVFrame的數(shù)據(jù)緩存空間通過(guò)調(diào)avcodec_decode_video分配和填充。
另一個(gè)AVFrame用來(lái)存放將解碼出來(lái)的原始數(shù)據(jù)變換為需要的數(shù)據(jù)格式(例如RGB,RGBA)的數(shù)據(jù),這個(gè)AVFrame需要手動(dòng)的分配數(shù)據(jù)緩存空間。

FFmpeg重要概念

PTS和DTS

PTS(Presentation Time Stamp):即顯示時(shí)間戳,這個(gè)時(shí)間戳用來(lái)告訴播放器該在什么時(shí)候顯示這一幀的數(shù)據(jù)。
DTS(Decoding Time Stamp):即解碼時(shí)間戳,這個(gè)時(shí)間戳的意義在于告訴播放器該在什么時(shí)候解碼這一幀的數(shù)據(jù)。
音頻的PTS和DTS是一致的,而含有B幀的視頻會(huì)存在DTS和PTS不一致的幀,我們這里主要通過(guò)PTS來(lái)控制播放時(shí)間。對(duì)解碼后的AVFrame使用av_frame_get_best_effort_timestamp可以獲取PTS,再乘以time_base即可得到實(shí)際時(shí)間。只有AVStream中獲取的time_base才是對(duì)的,其他地方獲取的可能會(huì)有問(wèn)題。
avformatCtx->streams[streamId]->time_base * pts

FFmpeg相關(guān)API解析

解碼相關(guān)

av_find_best_stream

獲取音視頻對(duì)應(yīng)的stream_index。
av_find_best_stream(fmt, type, -1, -1, NULL, 0);
fmtavformat_open_input獲取的AVFormatContext實(shí)例。

圖像內(nèi)存相關(guān)

av_image_alloc

此函數(shù)的功能是按照指定的寬、高、像素格式來(lái)申請(qǐng)圖像內(nèi)存。

int av_image_alloc(uint8_t *pointers[4], int linesizes[4],
                   int w, int h, enum AVPixelFormat pix_fmt, int align);

參數(shù)說(shuō)明如下:

pointers[4]:保存圖像通道的地址。如果是RGB,則前三個(gè)指針?lè)謩e指向R,G,B的內(nèi)存地址,第四個(gè)指針保留不用。
linesizes[4]:保存圖像每個(gè)通道的內(nèi)存對(duì)齊的步長(zhǎng),即一行的對(duì)齊內(nèi)存的寬度,此值大小等于圖像寬度。 
w:                 要申請(qǐng)內(nèi)存的圖像寬度。 
h:                  要申請(qǐng)內(nèi)存的圖像高度。 
pix_fmt:        要申請(qǐng)內(nèi)存的圖像的像素格式。 
align:            用于內(nèi)存對(duì)齊的值。 
返回值:所申請(qǐng)的內(nèi)存空間的總大小。如果是負(fù)值,表示申請(qǐng)失敗。

av_image_get_buffer_size

針對(duì)給圖像申請(qǐng)內(nèi)存,有另外一種方式,即使用av_image_get_buffer_sizeav_malloc來(lái)申請(qǐng)圖像所需的內(nèi)存。

int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align);

align:此參數(shù)是設(shè)定內(nèi)存對(duì)齊的對(duì)齊數(shù),也就是按多大的字節(jié)進(jìn)行內(nèi)存對(duì)齊。比如設(shè)置為1,表示按1字節(jié)對(duì)齊,那么得到的結(jié)果就是與實(shí)際的內(nèi)存大小一樣。再比如設(shè)置為4,表示按4字節(jié)對(duì)齊。也就是內(nèi)存的起始地址必須是4的整倍數(shù)。
av_image_get_buffer_size用來(lái)返回指定格式、寬高及對(duì)齊下的圖像所需空間的大小。使用樣例如下:

//計(jì)算指定像素格式、圖像寬、高所需要的對(duì)齊的內(nèi)存大小
int image_buf_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);   
//分配指定大小的內(nèi)存空間
out_buffer = (unsigned char *)av_malloc(image_buf_size);   

av_image_fill_arrays

/**
* Setup the data pointers and linesizes based on the specified image
* parameters and the provided array.
**/
int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4],
                         const uint8_t *src,
                         enum AVPixelFormat pix_fmt, int width, int height, int align);

該函數(shù)主要是按照指定的格式、寬高及對(duì)齊方式,將src的數(shù)據(jù)填充到dst_datadst_linesize里。參數(shù)具體說(shuō)明如下:

dst_data[4]:       [out]對(duì)申請(qǐng)的內(nèi)存格式化為三個(gè)通道后,分別保存其地址
dst_linesize[4]:    [out]格式化的內(nèi)存的步長(zhǎng)(即內(nèi)存對(duì)齊后的寬度)
src:                [in]av_alloc()函數(shù)申請(qǐng)的內(nèi)存地址。
pix_fmt:            [in] 申請(qǐng) src內(nèi)存時(shí)的像素格式
width:              [in]申請(qǐng)src內(nèi)存時(shí)指定的寬度
height:             [in]申請(qǐng)scr內(nèi)存時(shí)指定的高度
align:              [in]申請(qǐng)src內(nèi)存時(shí)指定的對(duì)齊字節(jié)數(shù)。

SWS圖像轉(zhuǎn)換相關(guān)

重點(diǎn)參考:雷霄驊的最簡(jiǎn)單的基于FFmpeg的libswscale的示例,其中有對(duì)圖像拉伸的算法SWS_BICUBIC、SWS_BILINEARSWS_POINT做了詳細(xì)的解釋,并對(duì)像素格式AV_PIX_FMT_有所描述。

sws_getContext

struct SwsContext *sws_getContext(
            int srcW, /* 輸入圖像的寬度 */
            int srcH, /* 輸入圖像的寬度 */
            enum AVPixelFormat srcFormat, /* 輸入圖像的像素格式 */
            int dstW, /* 輸出圖像的寬度 */
            int dstH, /* 輸出圖像的高度 */
            enum AVPixelFormat dstFormat, /* 輸出圖像的像素格式 */
            int flags,/* 選擇縮放算法(只有當(dāng)輸入輸出圖像大小不同時(shí)有效),一般選擇SWS_FAST_BILINEAR */
            SwsFilter *srcFilter, /* 輸入圖像的濾波器信息, 若不需要傳NULL */
            SwsFilter *dstFilter, /* 輸出圖像的濾波器信息, 若不需要傳NULL */
            const double *param /* 特定縮放算法需要的參數(shù)(?),默認(rèn)為NULL */
            );

初始化SwsContext的函數(shù)之一,成功返回SwsContext,否則返回NULL。參數(shù)說(shuō)明:
其中flags,可以設(shè)置為SWS_BICUBIC性能比較好,SWS_FAST_BILINEAR在性能和速度之間有一個(gè)比好好的平衡,而SWS_POINT的效果比較差。

sws_getCachedContext

struct SwsContext *sws_getCachedContext(struct SwsContext *context,
                                        int srcW, int srcH, enum AVPixelFormat srcFormat,
                                        int dstW, int dstH, enum AVPixelFormat dstFormat,
                                        int flags, SwsFilter *srcFilter,
                                        SwsFilter *dstFilter, const double *param);

初始化SwsContext的函數(shù)之一,成功返回SwsContext,否則返回NULL。參數(shù)說(shuō)明:

int srcW, /* 輸入圖像的寬度 */
int srcH, /* 輸入圖像的寬度 */
enum AVPixelFormat srcFormat, /* 輸入圖像的像素格式 */
int dstW, /* 輸出圖像的寬度 */
int dstH, /* 輸出圖像的高度 */
enum AVPixelFormat dstFormat, /* 輸出圖像的像素格式 */
int flags,/* 選擇縮放算法(只有當(dāng)輸入輸出圖像大小不同時(shí)有效),一般選擇SWS_FAST_BILINEAR */
SwsFilter *srcFilter, /* 輸入圖像的濾波器信息, 若不需要傳NULL */
SwsFilter *dstFilter, /* 輸出圖像的濾波器信息, 若不需要傳NULL */
const double *param /* 特定縮放算法需要的參數(shù)(?),默認(rèn)為NULL */

sws_scale

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
              const int srcStride[], int srcSliceY, int srcSliceH,
              uint8\_t *const dst[], const int dstStride[]);

該函數(shù)實(shí)現(xiàn)了三個(gè)功能:1.圖像色彩空間轉(zhuǎn)換;2.分辨率縮放;3.前后圖像濾波處理。
參數(shù)說(shuō)明如下:

  1. 參數(shù) SwsContext *c, 轉(zhuǎn)換格式的上下文。也就是 sws_getContext 函數(shù)返回的結(jié)果。
  2. 參數(shù) const uint8_t *const srcSlice[], 輸入圖像的每個(gè)顏色通道的數(shù)據(jù)指針。其實(shí)就是解碼后的AVFrame中的data[]數(shù)組。因?yàn)椴煌袼氐拇鎯?chǔ)格式不同,所以srcSlice[]維數(shù)也有可能不同。
    以YUV420P為例,它是planar格式,它的內(nèi)存中的排布如下:
    YYYYYYYY UUUU VVVV
    使用FFmpeg解碼后存儲(chǔ)在AVFramedata[]數(shù)組中時(shí):
    data[0]——-Y分量, Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8……
    data[1]——-U分量, U1, U2, U3, U4……
    data[2]——-V分量, V1, V2, V3, V4……
    像素格式名稱后面有“P”的,代表是planar格式,否則就是packed格式。Planar格式不同的分量分別存儲(chǔ)在不同的數(shù)組中,Packed格式的數(shù)據(jù)都存儲(chǔ)在同一個(gè)數(shù)組中,例如AV_PIX_FMT_RGB24存儲(chǔ)方式如下:
    data[0]: R1, G1, B1, R2, G2, B2, R3, G3, B3, R4, G4, B4……
    AVFrame中的linesize[]數(shù)組中保存的是對(duì)應(yīng)通道的數(shù)據(jù)寬度 ,
    linesize[0]——-Y分量的寬度
    linesize[1]——-U分量的寬度
    linesize[2]——-V分量的寬度
  3. 參數(shù)const int srcStride[],輸入圖像的每個(gè)顏色通道的跨度。也就是每個(gè)通道的行字節(jié)數(shù),對(duì)應(yīng)的是解碼后的AVFrame中的linesize[]數(shù)組。根據(jù)它可以確立下一行的起始位置,不過(guò)stride和width不一定相同,這是因?yàn)椋?br> a. 由于數(shù)據(jù)幀存儲(chǔ)的對(duì)齊,有可能會(huì)向每行后面增加一些填充字節(jié)這樣stride = width + N
    b. packet色彩空間下,每個(gè)像素幾個(gè)通道數(shù)據(jù)混合在一起,例如RGB24,每個(gè)像素3字節(jié)連續(xù)存放,因此下一行的位置需要跳過(guò)3*width字節(jié)。
  4. 參數(shù)int srcSliceY, int srcSliceH,定義在輸入圖像上處理區(qū)域,srcSliceY是起始位置,srcSliceH是處理多少行。
    如果srcSliceY=0; srcSliceH=height,表示一次性處理完整個(gè)圖像。這種設(shè)置是為了多線程并行,例如可以創(chuàng)建兩個(gè)線程,第一個(gè)線程處理 [0, h/2-1]行,第二個(gè)線程處理 [h/2, h-1]行。并行處理加快速度。
  5. 參數(shù)uint8_t *const dst[], const int dstStride[]定義輸出圖像信息(輸出的每個(gè)顏色通道數(shù)據(jù)指針,每個(gè)顏色通道行字節(jié)數(shù))

參考:FFmepg中的sws_scale() 函數(shù)分析

swr_convert

int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in , int in_count);

該函數(shù)主要是實(shí)現(xiàn)音頻文件的編碼轉(zhuǎn)換。

sws_setColorspaceDetails

初始化顏色空間

int sws_setColorspaceDetails(struct SwsContext *c, const int inv_table[4],
                             int srcRange, const int table[4], int dstRange,
                             int brightness, int contrast, int saturation);

c:需要設(shè)定的SwsContext。
inv_table:描述輸出YUV顏色空間的參數(shù)表。
srcRange:輸入圖像的取值范圍(“1”代表JPEG標(biāo)準(zhǔn),取值范圍是0-255;“0”代表MPEG標(biāo)準(zhǔn),取值范圍是16-235)。
table:描述輸入YUV顏色空間的參數(shù)表。
dstRange:輸出圖像的取值范圍。

時(shí)間戳相關(guān)

AVRational

FFMPEG的很多結(jié)構(gòu)中有AVRational time_base這樣的一個(gè)成員,它是AVRational結(jié)構(gòu)的

typedef struct AVRational {
    int num; ///< numerator 分?jǐn)?shù)
    int den; ///< denominator 分母
} AVRational;

AVRational這個(gè)結(jié)構(gòu)標(biāo)識(shí)一個(gè)分?jǐn)?shù),num為分?jǐn)?shù),den為分母。
實(shí)際上time_base的意思就是時(shí)間的刻度:
如(1,25),那么時(shí)間刻度就是1/25
(1,9000),那么時(shí)間刻度就是1/90000
那么,在刻度為1/25的體系下的time=5,轉(zhuǎn)換成在刻度為1/90000體系下的時(shí)間time為(51/25)/(1/90000) = 36005=18000,即下面說(shuō)到的函數(shù)av_rescale_q。
參考:FFMPEG之TimeBase成員理解

av_rescale_q

// The operation is mathematically equivalent to `a * bq / cq`.
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;

該函數(shù)的作用是在不同的時(shí)間基之間進(jìn)行轉(zhuǎn)換。a表示要原始值,bq表示原來(lái)的時(shí)間基準(zhǔn);cq表示要轉(zhuǎn)換到的時(shí)間基準(zhǔn)。

  • 內(nèi)部時(shí)間基準(zhǔn):AV_TIME_BASE
    AVFormatContext中的參數(shù)多是基于AV_TIME_BASE的。
  • 流時(shí)間基準(zhǔn):AVStream->time_base
    AVStream、AVPacketAVFrame中的時(shí)間戳多是基于對(duì)應(yīng)的流時(shí)間基準(zhǔn)。

參考:FFmpeg時(shí)間戳

操作相關(guān)

av_seek_frame

int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);

參數(shù)說(shuō)明:

s              // 描述了一個(gè)媒體文件或媒體流的構(gòu)成和基本信息
stream_index   //表示當(dāng)前的seek是針對(duì)哪個(gè)基本流,比如視頻或者音頻等等
timestamp      //將要定位的時(shí)間戳,時(shí)間戳?xí)囊訟V_TIME_BASE為單位向具體流的時(shí)間基自動(dòng)轉(zhuǎn)換。
flags          //
              AVSEEK_FLAG_BACKWARD  是seek到請(qǐng)求的timestamp之前最近的關(guān)鍵幀
              AVSEEK_FLAG_BYTE      是基于字節(jié)位置的查找 
              AVSEEK_FLAG_ANY       是可以seek到任意幀,注意不一定是關(guān)鍵幀,因此使用時(shí)可能會(huì)導(dǎo)致花屏    
              AVSEEK_FLAG_FRAME     是基于幀數(shù)量快進(jìn)

在進(jìn)行seek操作時(shí),時(shí)間戳必須基于流時(shí)間基準(zhǔn)(AVStream->time_base),比如:seek到第5秒,需做如下的操作:

// 首先計(jì)算出基于視頻流時(shí)間基準(zhǔn)的時(shí)間戳
int64_t timestamp = av_rescale_q(5 * AV_TIME_BASE, AV_TIME_BASE_Q, video_stream_->time_base);
// 然后seek
av_seek_frame(av_format_context, video_stream_index, timestamp, AVSEEK_FLAG_BACKWARD);

avcodec_flush_buffers

void avcodec_flush_buffers(AVCodecContext *avctx);

說(shuō)明:回放過(guò)程中跳轉(zhuǎn)播放,或者切換到一個(gè)不同的流,調(diào)用此函數(shù),清空內(nèi)部緩存的幀數(shù)據(jù)。
實(shí)際應(yīng)用場(chǎng)景:在網(wǎng)絡(luò)環(huán)境中,傳輸數(shù)據(jù)用UDP經(jīng)常有丟包,而丟包很容易造成FFmpeg解碼器緩沖的幀數(shù)增加,可以在一段時(shí)間內(nèi)清空解碼器緩存
切記:應(yīng)該在接收到I幀的時(shí)候,清空緩存,否則出現(xiàn)畫(huà)面不連續(xù)或者馬賽克的情況

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容