2023-04-18:ffmpeg中的hw_decode.c的功能是通过使用显卡硬件加速器(如 NVIDIA CUDA、Intel Quick Sync Video 等)对视频进行解码,从而提高解码效
2023-04-18:ffmpeg中的hw_decode.c的功能是通過使用顯卡硬件加速器(如 NVIDIA CUDA、Intel Quick Sync Video 等)對視頻進行解碼,從而提高解碼效率和性能。在進行硬件加速解碼時,相較于 CPU 的軟件解碼方式,GPU 可以利用其并行處理能力和更高的帶寬進行更高效的解碼操作。請用go語言改寫hw_decode.c文件。
答案2023-04-18:
hw_decode.c 功能和執行過程
ffmpeg 中的 hw_decode.c 代碼,其功能是通過使用顯卡硬件加速器對視頻進行解碼,從而提高解碼效率和性能。下面將分步驟描述該代碼的功能和執行過程。
引入頭文件
代碼開頭引入了必要的頭文件,包括 libavcodec/avcodec.h、libavformat/avformat.h、libavutil/pixdesc.h 等,這些頭文件定義了解碼和編碼相關的結構體和函數。
初始化變量和數據
接下來的一段代碼初始化了一些變量和數據,例如 hw_device_ctx 是顯卡設備上下文的引用,hw_pix_fmt 是像素格式等。它們都將在后面的代碼中使用到。
硬件加速器初始化
在 hw_decoder_init 函數中,調用 av_hwdevice_ctx_create 創建指定類型的硬件加速器,并將它保存到 ctx->hw_device_ctx 所指向的 AVBufferRef 緩沖區中。
獲取硬件支持的像素格式
在 get_hw_format 函數中,遍歷 pix_fmts 數組,查找是否有與 hw_pix_fmt 相等的像素格式,如果找到則返回該像素格式,否則返回 AV_PIX_FMT_NONE。
解碼和輸出
decode_write 函數是該代碼的核心部分,實現了解碼和輸出功能。首先調用 avcodec_send_packet 將輸入的 packet 數據發送給解碼器,然后進入一個無限循環,直到所有數據都被解碼并輸出。在循環中,先調用 av_frame_alloc 分配 AVFrame 幀空間,然后調用 avcodec_receive_frame 從解碼器中接收已解碼的幀數據。如果返回的是 EAGAIN 或 EOF,則退出循環;如果出現錯誤則跳轉到 fail 標簽處處理。如果解碼得到的幀格式與硬件支持的像素格式相同,則將該幀數據從 GPU 拷貝到 CPU 上,再調用 av_image_copy_to_buffer 將幀數據復制到內存緩沖區中,并通過 fwrite 函數將數據寫入文件中。最后通過 av_frame_free 和 av_freep 函數釋放內存空間。
主函數
main 函數首先解析命令行參數,包括設備類型、輸入文件名和輸出文件名。然后通過 avformat_open_input 打開輸入文件,通過 av_find_best_stream 查找視頻流,并獲取硬件支持的像素格式。接下來創建 AVCodexContext 上下文,設置 get_format 回調函數和硬件加速器上下文。通過 avcodec_open2 打開解碼器,并打開輸出文件。最后通過 av_read_frame 讀取文件數據,調用 decode_write 函數進行解碼和輸出,直到讀取完畢。
綜上所述,該代碼實現了使用顯卡硬件加速器對視頻進行解碼的功能,并通過調用相關的結構體和函數實現了硬件加速器的初始化、解碼和輸出等操作。其主要思路是將顯卡的并行處理能力和更高的帶寬用于視頻解碼,從而提高解碼效率和性能。
go代碼如下:
github/moonfdd/ffmpeg-go庫,把hw_decode.c改寫成了go代碼。如下:
package mainimport ("fmt""os""unsafe""github.com/moonfdd/ffmpeg-go/ffcommon""github.com/moonfdd/ffmpeg-go/libavcodec""github.com/moonfdd/ffmpeg-go/libavformat""github.com/moonfdd/ffmpeg-go/libavutil" )func main0() (ret ffcommon.FInt) {var input_ctx *libavformat.AVFormatContextvar video_stream ffcommon.FIntvar video *libavformat.AVStreamvar decoder_ctx *libavcodec.AVCodecContextvar decoder *libavcodec.AVCodecvar packet libavformat.AVPacketvar type0 libavutil.AVHWDeviceTypevar i ffcommon.FIntif len(os.Args) < 4 {fmt.Printf("Usage: %s <device type> <input file> <output file>\n", os.Args[0])return -1}type0 = libavutil.AvHwdeviceFindTypeByName(os.Args[1])if type0 == libavutil.AV_HWDEVICE_TYPE_NONE {fmt.Printf("Device type %s is not supported.\n", os.Args[1])fmt.Printf("Available device types:")type0 = libavutil.AvHwdeviceIterateTypes(type0)for type0 != libavutil.AV_HWDEVICE_TYPE_NONE {fmt.Printf(" %s", libavutil.AvHwdeviceGetTypeName(type0))type0 = libavutil.AvHwdeviceIterateTypes(type0)}fmt.Printf("\n")return -1}/* open the input file */if libavformat.AvformatOpenInput(&input_ctx, os.Args[2], nil, nil) != 0 {fmt.Printf("Cannot open input file '%s'\n", os.Args[2])return -1}if input_ctx.AvformatFindStreamInfo(nil) < 0 {fmt.Printf("Cannot find input stream information.\n")return -1}/* find the video stream information */ret = input_ctx.AvFindBestStream(libavutil.AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0)if ret < 0 {fmt.Printf("Cannot find a video stream in the input file\n")return -1}video_stream = retfor i = 0; ; i++ {config := decoder.AvcodecGetHwConfig(i)if config == nil {fmt.Printf("Decoder %s does not support device type %s.\n",ffcommon.StringFromPtr(decoder.Name), libavutil.AvHwdeviceGetTypeName(type0))return -1}if config.Methods&libavcodec.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX != 0 && config.DeviceType == type0 {hw_pix_fmt = config.PixFmtbreak}}decoder_ctx = decoder.AvcodecAllocContext3()if decoder_ctx == nil {return -libavutil.ENOMEM}video = input_ctx.GetStream(uint32(video_stream))if decoder_ctx.AvcodecParametersToContext(video.Codecpar) < 0 {return -1}decoder_ctx.GetFormat = ffcommon.NewCallback(get_hw_format)if hw_decoder_init(decoder_ctx, type0) < 0 {return -1}ret = decoder_ctx.AvcodecOpen2(decoder, nil)if ret < 0 {fmt.Printf("Failed to open codec for stream #%d\n", video_stream)return -1}/* open the file to dump raw data */output_file, _ = os.Create(os.Args[3])/* actual decoding and dump the raw data */for ret >= 0 {ret = input_ctx.AvReadFrame(&packet)if ret < 0 {break}if uint32(video_stream) == packet.StreamIndex {ret = decode_write(decoder_ctx, &packet)}packet.AvPacketUnref()}/* flush the decoder */packet.Data = nilpacket.Size = 0ret = decode_write(decoder_ctx, &packet)packet.AvPacketUnref()if output_file != nil {output_file.Close()}libavcodec.AvcodecFreeContext(&decoder_ctx)libavformat.AvformatCloseInput(&input_ctx)libavutil.AvBufferUnref(&hw_device_ctx)return 0 }var hw_device_ctx *libavutil.AVBufferRef var hw_pix_fmt libavutil.AVPixelFormat var output_file *os.Filefunc hw_decoder_init(ctx *libavcodec.AVCodecContext, type0 libavutil.AVHWDeviceType) ffcommon.FInt {var err ffcommon.FInt = 0err = libavutil.AvHwdeviceCtxCreate(&hw_device_ctx, type0, "", nil, 0)if err < 0 {fmt.Printf("Failed to create specified HW device.\n")return err}ctx.HwDeviceCtx = hw_device_ctx.AvBufferRef()return err }func get_hw_format(ctx *libavcodec.AVCodecContext, pix_fmts *libavutil.AVPixelFormat) uintptr {var p *libavutil.AVPixelFormatfor p = pix_fmts; *p != -1; p = (*libavutil.AVPixelFormat)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + uintptr(4))) {if *p == hw_pix_fmt {return uintptr(*p)}}fmt.Printf("Failed to get HW surface format.\n")r := libavutil.AVPixelFormat(libavutil.AV_PIX_FMT_NONE)return uintptr(r) }func decode_write(avctx *libavcodec.AVCodecContext, packet *libavcodec.AVPacket) ffcommon.FInt {var frame, sw_frame *libavutil.AVFramevar tmp_frame *libavutil.AVFramevar buffer *ffcommon.FUint8Tvar size ffcommon.FIntvar ret ffcommon.FInt = 0var e errorret = avctx.AvcodecSendPacket(packet)if ret < 0 {fmt.Printf("Error during decoding\n")return ret}for {frame = libavutil.AvFrameAlloc()sw_frame = libavutil.AvFrameAlloc()if frame == nil || sw_frame == nil {fmt.Printf("Can not alloc frame\n")ret = -libavutil.ENOMEMgoto fail}ret = avctx.AvcodecReceiveFrame(frame)if ret == -libavutil.EAGAIN || ret == libavutil.AVERROR_EOF {libavutil.AvFrameFree(&frame)libavutil.AvFrameFree(&sw_frame)return 0} else if ret < 0 {fmt.Printf("Error while decoding\n")goto fail}if frame.Format == hw_pix_fmt {/* retrieve data from GPU to CPU */ret = libavutil.AvHwframeTransferData(sw_frame, frame, 0)if ret < 0 {fmt.Printf("Error transferring the data to system memory\n")goto fail}tmp_frame = sw_frame} else {tmp_frame = frame}size = libavutil.AvImageGetBufferSize(tmp_frame.Format, tmp_frame.Width,tmp_frame.Height, 1)buffer = (*byte)(unsafe.Pointer(libavutil.AvMalloc(uint64(size))))if buffer == nil {fmt.Printf("Can not alloc buffer\n")ret = -libavutil.ENOMEMgoto fail}ret = libavutil.AvImageCopyToBuffer(buffer, size,(*[4]*byte)(unsafe.Pointer(&tmp_frame.Data)),(*[4]int32)(unsafe.Pointer(&tmp_frame.Linesize)), tmp_frame.Format,tmp_frame.Width, tmp_frame.Height, 1)if ret < 0 {fmt.Printf("Can not copy image to buffer\n")goto fail}_, e = output_file.Write(ffcommon.ByteSliceFromByteP(buffer, int(size)))if e != nil {fmt.Printf("Failed to dump raw data.\n")goto fail}fail:libavutil.AvFrameFree(&frame)libavutil.AvFrameFree(&sw_frame)libavutil.AvFreep(uintptr(unsafe.Pointer(&buffer)))if ret < 0 {return ret}} }func main() {// go run ./examples/internalexamples/hw_decode/main.go cuda ./resources/big_buck_bunny.mp4 ./out/hw.yuv// ./lib/ffplay -pixel_format yuv420p -video_size 640x360 ./out/hw.yuvos.Setenv("Path", os.Getenv("Path")+";./lib")ffcommon.SetAvutilPath("./lib/avutil-56.dll")ffcommon.SetAvcodecPath("./lib/avcodec-58.dll")ffcommon.SetAvdevicePath("./lib/avdevice-58.dll")ffcommon.SetAvfilterPath("./lib/avfilter-56.dll")ffcommon.SetAvformatPath("./lib/avformat-58.dll")ffcommon.SetAvpostprocPath("./lib/postproc-55.dll")ffcommon.SetAvswresamplePath("./lib/swresample-3.dll")ffcommon.SetAvswscalePath("./lib/swscale-5.dll")genDir := "./out"_, err := os.Stat(genDir)if err != nil {if os.IsNotExist(err) {os.Mkdir(genDir, 0777) // Everyone can read write and execute}}main0() }執行命令如下:
go run ./examples/internalexamples/hw_decode/main.go cuda ./resources/big_buck_bunny.mp4 ./out/hw.yuv ./lib/ffplay -pixel_format yuv420p -video_size 640x360 ./out/hw.yuv
解碼出來的視頻,看起來有點失真的。
代碼分析
首先,我們需要導入所需的庫文件。在主函數中,我們首先檢查輸入參數數量是否正確,如果不正確則輸出使用說明并返回錯誤。
接下來,我們通過設備類型名稱獲取設備類型,如果不支持該設備類型,則輸出可用設備類型列表并返回錯誤。
在打開輸入文件之后,我們使用AvFindBestStream函數查找最佳視頻流,并使用其參數初始化解碼器并打開解碼器。
我們得到每幀數據之后,解碼函數AvcodecSendPacket和AvcodecReceiveFrame會被循環調用,以將解碼后的幀數據寫入輸出文件。
最后,我們關閉所有打開的資源,包括輸入、輸出文件和解碼器等。
結語
本文介紹了如何使用Golang實現FFmpeg硬解碼程序。通過對FFmpeg官方的HW Decode示例進行適當修改,我們成功地完成了設備類型檢查、輸入文件打開、解碼器配置和輸出文件處理等功能。此外,我們也介紹了如何在實際應用中使用FFmpeg庫,并提供了一些代碼片段供讀者參考。
總結
以上是生活随笔為你收集整理的2023-04-18:ffmpeg中的hw_decode.c的功能是通过使用显卡硬件加速器(如 NVIDIA CUDA、Intel Quick Sync Video 等)对视频进行解码,从而提高解码效的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QGIS实现tiff文件转png、jpg
- 下一篇: 人体最佳睡眠时段