日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ffmpeg 自定义IO与Seek

發布時間:2023/12/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ffmpeg 自定义IO与Seek 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

AvIOContext

使用場景是: 使用 ffmpeg 相關解碼代碼要編譯成 wasm 在瀏覽器端使用,js 層面拿到視頻 buffer 數據(拉取的 m3u8 分片也好,本地上傳的視頻文件等等),將 buffer 傳遞給 c 解封裝、解碼,這時候就用到 AVIOContext

AVIOContext

AVIOContext 主要使用邏輯: 我們有一塊大的視頻文件 buffer,然后 ffmpeg 對這塊數據的訪問借助于 io 上下文,io 上下文上自己維護了一個小的 buffer(注意:這個小的buffer也是需要我們手動給io上下文分配),之后 ffmpeg 內部解封轉、解碼需要的數據都從io上下文這個小buffer要, io 上下文上這個小 buffer 數據不足時又自己從我們的視頻大 buffer 中不斷補充數據

先看此上下文結構體中一些重要的屬性:

AVIOContext *avioCtx;avioCtx->buffer // 即io上下文中那個小buffer,通過avio_alloc_context()來分配io上下文時作為參數傳遞avioCtx->buffer_size // 小buffer的大小 avioCtx->buf_ptr // io上下文的小buffer中的數據當前被消耗的位置 avioCtx->buf_end // io上下文的小buffer數據的結束位置 avioCtx->opaque // ** 是一個自定義的結構體,存儲視頻大buffer的信息,如buffer開始位置,視頻buffer長度,這個結構會回傳給 read_packet、write_packet、seek回調函數!!! **, avioCtx->read_packet // 需要自己實現的一個 `用視頻大buffer數據` 填充 `io上下文小buffer`的回調函數 avioCtx->write_packet //自己實現 把io上下文中的小buffer數據寫到某處的回調函數 avioCtx->seek // 也是自己實現的 用來在視頻大buffer中seek的函數幾個回調函數使用見后面介紹

avio.h 中關于 io 上下文 buffer 主要概念的圖示:

avio_alloc_context()

方法簽名:

AVIOContext *avio_alloc_context(unsigned char *buffer,int buffer_size,int write_flag,void *opaque,int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),int64_t (*seek)(void *opaque, int64_t offset, int whence));

這幾個參數分別對應了上面結構體介紹中的對應屬性,看實際使用流程:

io 上下文使用流程

  • 定義一個結構體,存儲視頻大 buffer 數據相關信息

  • typedef struct _BufferData {uint8_t *ptr; // 指向buffer數據中 `還沒被io上下文消耗的位置`uint8_t *ori_ptr; // 也是指向buffer數據的指針,之所以定義ori_ptr,是用在自定義seek函數中size_t size; // 視頻buffer還沒被消耗部分的大小,隨著不斷消耗,越來越小size_t file_size; //原始視頻buffer的大小,也是用在自定義seek函數中 } BufferData;// 拿到視頻buffer數據及長度 定義bd 存儲相關信息 BufferData bd; bd.ptr = buffer bd.ori_ptr = buffer bd.size =xxxx bd.file_size=xxxx
  • 分配 io 上下文使用的小 buffer

  • #define IO_CTX_BUFFER_SIZE 4096 * 4; uint8_t *ioCtxBuffer = av_malloc(IO_CTX_BUFFER_SIZE);
  • 自定義 read_packet() 、seek()方法 (自定義 seek 方法下面 seek 部分介紹)

  • static int read_packet(void *opaque, uint8_t *buf, int buf_size) {BufferData *bd = (BufferData *)opaque;buf_size = MIN(bd->size, buf_size);if (!buf_size){printf("no buf_size pass to read_packet,%d,%d\n", buf_size, bd->size);return -1;}printf("ptr in file:%p io.buffer ptr:%p, size:%ld,buf_size:%ld\n", bd->ptr, buf, bd->size, buf_size);memcpy(buf, bd->ptr, buf_size);bd->ptr += buf_size;bd->size -= buf_size; // left size in bufferreturn buf_size; } 主要實現功能就是從視頻 buffer 某個位置開始,copy 一段數據到 io 上下文小 buffer

    參數介紹:

    opaque: 指向 自定義 BufferData 結構體的實例,因為要從視頻 buffer 數據中不斷通過 read_packet 讀數據到 io 上下文小 buffer。

    buf: io 上下文小 buffer 的開始位置,也就是上面定義的ioCtxBuffer,這個位置一直不變,小buffer的數據不斷被覆蓋

    buf_size: 就是 io 上下文小 buffer 的大小,如上定義的IO_CTX_BUFFER_SIZE

  • 分配 io 上下文

  • AVIOContext *avioCtx; avioCtx = avio_alloc_context(ioCtxBuffer, IO_CTX_BUFFER_SIZE, 0, &bd, &read_packet, NULL,NULL);
  • 創建 AVFormatContext,并掛載 io 上下文

  • AVFormatContext *fmtCtx = avformat_alloc_context(); fmtCtx.pb = avioCtx fmtCtx->flags |= AVFMT_FLAG_CUSTOM_IO;

    至此,就通過自定義 io 上下文,讓 AVFormatContext 可以解封裝提供的視頻 buffer 數據了,之后的解封裝、解碼流程和不使用 io 上下文一樣

    seek

    seek 功能研究卡住了幾天,原因有二,1: AVIOContext 自定義 seek 函數的實現邏輯不清楚, 2: 對 ts 格式文件 精準 seek 存在花屏或解碼失敗問題,原以為自己實現邏輯存在問題,實際上對于 ts 格式,沒有像 mp4 一樣有地方存儲所有關鍵幀的位置偏移信息,ffmpeg 也無能為力

    ffmpeg 中 seek 功能通過 av_seek_frame()方法來進行

    /*** Seek to the keyframe at timestamp.* 'timestamp' in 'stream_index'.** @param s media file handle* @param stream_index If stream_index is (-1), a default* stream is selected, and timestamp is automatically converted* from AV_TIME_BASE units to the stream specific time_base.* @param timestamp Timestamp in AVStream.time_base units* or, if no stream is specified, in AV_TIME_BASE units.* @param flags flags which select direction and seeking mode* @return >= 0 on success*/ int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,int flags);/*** Seek to timestamp ts.* Seeking will be done so that the point from which all active streams* can be presented successfully will be closest to ts and within min/max_ts.* Active streams are all streams that have AVStream.discard < AVDISCARD_ALL.** If flags contain AVSEEK_FLAG_BYTE, then all timestamps are in bytes and* are the file position (this may not be supported by all demuxers).* If flags contain AVSEEK_FLAG_FRAME, then all timestamps are in frames* in the stream with stream_index (this may not be supported by all demuxers).* Otherwise all timestamps are in units of the stream selected by stream_index* or if stream_index is -1, in AV_TIME_BASE units.* If flags contain AVSEEK_FLAG_ANY, then non-keyframes are treated as* keyframes (this may not be supported by all demuxers).* If flags contain AVSEEK_FLAG_BACKWARD, it is ignored.

    AVIOContext 自定義 seek 函數

    io 上下文 seek 函數的主要邏輯是: 原始視頻大 buffer 和長度知道,通過 seek 方法來把大 buffer 的要讀取位置指定到某個位置

    回調簽名:

    int64_t(* seek )(void *opaque, int64_t offset, int whence)

    參數介紹:

    opaque: 同 read_packet 回調,原始視頻 buffer 信息的結構體

    offset: 要 seek 到的位置,可以是相對原始視頻的起始位置,可以是相對 io 上下文小 buffer 的起始位置,取決于 whence

    whence: seek 的類型,取值為 AVSEEK_SIZE 、SEEK_CUR 、SEEK_SET、SEEK_END,

    AVSEEK_SIZE: 不進行 seek 操作,而是要求返回 視頻 buffer 的長度大小

    SEEK_CUR: 表示 offset 是相對 io 上下文小 buffer 開始位置的

    SEEK_SET: 表示 offset 是相對 原始 buffer 開始位置的

    SEEK_END: 表示 offset 是相對 原始 buffer 結束位置的

    通過 debug av_seek_frame --> seek_frame_internal ---> seek_frame_byte --->avio_seek() 發現對 iocontext 自定義的 seek 方法是在 avio_seek() 中使用的。發現 avio_seek 中調用 ioContext->seek()時 whence 只會傳遞 AVSEEK_SIZE 或 SEEK_SET

    所以自定義 io seek 函數實現如下即可:

    static int64_t seek_in_buffer(void *opaque, int64_t offset, int whence) {BufferData *bd = (BufferData *)opaque;int64_t ret = -1;// printf("whence=%d , offset=%lld , file_size=%ld\n", whence, offset, bd->file_size);switch (whence){case AVSEEK_SIZE:ret = bd->file_size;break;case SEEK_SET:bd->ptr = bd->ori_ptr + offset;bd->size = bd->file_size - offset;ret = bd->ptr;break;}return ret; }

    精準 seek

    av_seek_frame 要想不花屏需要設置 flagAVSEEK_FLAG_BACKWARD

    對于 mp4 格式沒毛病,seek 到里指定 pts 之前最近的關鍵幀,然后開始解碼,從關鍵幀到指定的 pts 之前的視頻幀可以手動丟棄,然后從指定 pts 位置開始展示

    對于 ts 格式效果就沒那么好,av_seek_frame() 對 ts 是會精確的 seek 到指定的 pts 位置的,但找不到 pts 之前最近的關鍵幀,指定 AVSEEK_FLAG_BACKWARD 也不行,效果就是: 從指定的 pts 位置開始解碼,花屏直到遇到下一個關鍵幀,對于單個 ts 分片,只在開頭有一個關鍵幀的這種,seek 后可能從指定的 pts 位置開始直接全部解碼失敗了。測試對一個 ts 文件 通過 ffplayffplay -ss 秒數 -i filepath進行 seek 播放,要么只有聲音播放沒有畫面、要么先花屏一會。所以一般的 hls ts seek 操作都是不精準 seek,跨分片的。

    最后,av_seek_frame()后需要刷新解碼器上下文

    avcodec_flush_buffers(audioState.codecCtx); avcodec_flush_buffers(videoState.codecCtx);

    總結

    以上是生活随笔為你收集整理的ffmpeg 自定义IO与Seek的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。