Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 24개 있습니다.)
.NET Framework: 1129. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 인코딩 예제(encode_video.c)
; https://www.sysnet.pe.kr/2/0/12898

.NET Framework: 1134. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c)
; https://www.sysnet.pe.kr/2/0/12924

.NET Framework: 1135. C# - ffmpeg(FFmpeg.AutoGen)로 하드웨어 가속기를 이용한 비디오 디코딩 예제(hw_decode.c)
; https://www.sysnet.pe.kr/2/0/12932

.NET Framework: 1136. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP2 오디오 파일 디코딩 예제(decode_audio.c)
; https://www.sysnet.pe.kr/2/0/12933

.NET Framework: 1137. ffmpeg의 파일 해시 예제(ffhash.c)를 C#으로 포팅
; https://www.sysnet.pe.kr/2/0/12935

.NET Framework: 1138. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 멀티미디어 파일의 메타데이터를 보여주는 예제(metadata.c)
; https://www.sysnet.pe.kr/2/0/12936

.NET Framework: 1139. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 오디오(mp2) 인코딩하는 예제(encode_audio.c)
; https://www.sysnet.pe.kr/2/0/12937

.NET Framework: 1147. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 오디오 필터링 예제(filtering_audio.c)
; https://www.sysnet.pe.kr/2/0/12951

.NET Framework: 1148. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 오디오 필터 예제(filter_audio.c)
; https://www.sysnet.pe.kr/2/0/12952

.NET Framework: 1150. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12959

개발 환경 구성: 637. ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 세 번째 이야기
; https://www.sysnet.pe.kr/2/0/12960

.NET Framework: 1151. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 프레임의 크기 및 포맷 변경 예제(scaling_video.c)
; https://www.sysnet.pe.kr/2/0/12961

.NET Framework: 1153. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 avio_reading.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12964

.NET Framework: 1157. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 muxing.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12971

.NET Framework: 1159. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 qsvdec.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12975

.NET Framework: 1161. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 resampling_audio.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12978

.NET Framework: 1166. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 filtering_video.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12984

.NET Framework: 1169. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 demuxing_decoding.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12987

.NET Framework: 1170. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcode_aac.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12991

.NET Framework: 1178. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 http_multiclient.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13002

.NET Framework: 1180. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 remuxing.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13006

.NET Framework: 1188. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcoding.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13023

.NET Framework: 1190. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 vaapi_encode.c, vaapi_transcode.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13025

.NET Framework: 1191. C 언어로 작성된 FFmpeg Examples의 C# 포팅 전체 소스 코드
; https://www.sysnet.pe.kr/2/0/13026




C# - ffmpeg(FFmpeg.AutoGen)로 하드웨어 가속기를 이용한 비디오 디코딩 예제(hw_decode.c)

지난 글에 이어,

C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c)
; https://www.sysnet.pe.kr/2/0/12924

이번에는 하드웨어 가속기를 이용한 디코딩 예제를 변환해 보겠습니다.

hw_decode.c
; https://ffmpeg.org/doxygen/trunk/hw_decode_8c-example.html

변환한 C# 코드의 전문은 다음과 같습니다.

using FFmpeg.AutoGen;
using FFmpeg.AutoGen.Example;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace FFmpegApp1
{
    internal unsafe class Program
    {
        static AVPixelFormat _hw_pix_fmt = AVPixelFormat.AV_PIX_FMT_NONE;
        static AVBufferRef* _hw_device_ctx = null;

        static void Main(string[] args)
        {
            FFmpegBinariesHelper.RegisterFFmpegBinaries();

#if DEBUG
            Console.WriteLine("Current directory: " + Environment.CurrentDirectory);
            Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32");
            Console.WriteLine($"FFmpeg version info: {ffmpeg.av_version_info()}");
#endif
            Console.WriteLine();

            Console.WriteLine($"LIBAVFORMAT Version: {ffmpeg.LIBAVFORMAT_VERSION_MAJOR}.{ffmpeg.LIBAVFORMAT_VERSION_MINOR}");

            string outputPath = @"c:\temp\output";
            try
            {
                Directory.Delete(outputPath, true);
                Directory.CreateDirectory(outputPath);
            }
            catch { }

            string outputFilePath = Path.Combine(outputPath, "test_cs2.dat");

            /* hwdevice type: cuda, dxva2, d3d11va, opencl */
            video_decode_example("cuda", @"D:\video_sample\theade-i-was-young.mp4", outputFilePath);
        }

        static unsafe void video_decode_example(string hwdeviceName, string filename, string outputFileName)
        {
            AVFormatContext* input_ctx = null;
            AVStream* video = null;
            AVCodecContext* decoder_ctx = null;
            AVCodec* decoder = null;
            AVPacket* packet = null;

            // https://ffmpeg.org/doxygen/trunk/codec_8h_source.html#l00420
            int AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX = 0x01;
            int ret = 0;

            AVHWDeviceType type = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE;

            Console.WriteLine("Available device types:");
            while ((type = ffmpeg.av_hwdevice_iterate_types(type)) != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
            {
                Console.WriteLine($"\t{ffmpeg.av_hwdevice_get_type_name(type)}");
            }

            type = ffmpeg.av_hwdevice_find_type_by_name(hwdeviceName);
            if (type == AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
            {
                Console.WriteLine($"Device type {hwdeviceNae} is not supported.");
                return;
            }

            do
            {
                packet = ffmpeg.av_packet_alloc();
                if (packet == null)
                {
                    Console.WriteLine("Failed to allocate AVPacket");
                    break;
                }

                if (ffmpeg.avformat_open_input(&input_ctx, filename, null, null) < 0)
                {
                    Console.WriteLine($"Cannot open input file: {filename}");
                    break;
                }

                if (ffmpeg.avformat_find_stream_info(input_ctx, null) < 0)
                {
                    Console.WriteLine("Cannot find input stream information.");
                    break;
                }

                /* find the video stream information */
                ret = ffmpeg.av_find_best_stream(input_ctx, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
                if (ret < 0)
                {
                    Console.WriteLine("Cannot find a video stream in the input file\n");
                    break;
                }

                int video_stream = ret;

                for (int i = 0; ; i++)
                {
                    AVCodecHWConfig* config = ffmpeg.avcodec_get_hw_config(decoder, i);
                    if (config == null)
                    {
                        string decoderName = Marshal.PtrToStringUTF8(new IntPtr(decoder->name));
                        Console.WriteLine($"Decoder {decoderName} does not support device type {ffmpeg.av_hwdevice_get_type_name(type)}");
                        break;
                    }

                    if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) == AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
                            config->device_type == type)
                    {
                        _hw_pix_fmt = config->pix_fmt;
                        break;
                    }
                }

                if (_hw_pix_fmt == AVPixelFormat.AV_PIX_FMT_NONE)
                {
                    break;
                }

                decoder_ctx = ffmpeg.avcodec_alloc_context3(decoder);
                if (decoder_ctx == null)
                {
                    Console.WriteLine("Could not allocate video codec context");
                    break;
                }

                video = input_ctx->streams[video_stream];
                if (ffmpeg.avcodec_parameters_to_context(decoder_ctx, video->codecpar) < 0)
                {
                    break;
                }

                decoder_ctx->get_format = (AVCodecContext_get_format_func)get_hw_format;

                if (hw_decoder_init(decoder_ctx, type) < 0)
                {
                    break;
                }

                if ((ret = ffmpeg.avcodec_open2(decoder_ctx, decoder, null)) < 0)
                {
                    Console.WriteLine($"Failed to open codec for stream {video_stream}");
                    break;
                }

                using FileStream output_file = File.OpenWrite(outputFileName);

                while (ret >= 0)
                {
                    if ((ret = ffmpeg.av_read_frame(input_ctx, packet)) < 0)
                    {
                        break;
                    }

                    if (video_stream == packet->stream_index)
                    {
                        ret = decode_write(decoder_ctx, packet, output_file);
                    }
                }

                ffmpeg.av_packet_unref(packet);

                /* flush the decoder */
                ret = decode_write(decoder_ctx, null, output_file);

            } while (false);

            if (packet != null)
            {
                ffmpeg.av_packet_free(&packet);
            }

            if (decoder_ctx != null)
            {
                ffmpeg.avcodec_free_context(&decoder_ctx);
            }

            if (input_ctx != null)
            {
                ffmpeg.avformat_close_input(&input_ctx);
            }

            if (_hw_device_ctx != null)
            {
                fixed (AVBufferRef** ppRef = &_hw_device_ctx)
                {
                    ffmpeg.av_buffer_unref(ppRef);
                }
            }
        }

        static unsafe int decode_write(AVCodecContext* avctx, AVPacket* packet, FileStream output_file)
        {
            AVFrame* frame = null;
            AVFrame* sw_frame = null;
            AVFrame* tmp_frame = null;
            byte* buffer = null;
            int size = 0;
            int ret = 0;

            ret = ffmpeg.avcodec_send_packet(avctx, packet);
            if (ret < 0)
            {
                Console.WriteLine("Error during decoding");
                return ret;
            }

            while (true)
            {
                if ((frame = ffmpeg.av_frame_alloc()) == null || (sw_frame = ffmpeg.av_frame_alloc()) == null)
                {
                    Console.WriteLine("Can not alloc frame");
                    ret = ffmpeg.AVERROR(ffmpeg.ENOMEM);
                    goto fail;
                }

                ret = ffmpeg.avcodec_receive_frame(avctx, frame);
                if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)
                {
                    ffmpeg.av_frame_free(&frame);
                    ffmpeg.av_frame_free(&sw_frame);
                    return 0;
                }
                else if (ret < 0)
                {
                    Console.WriteLine("Error while decoding");
                    goto fail;
                }

                if (frame->format == (int)_hw_pix_fmt)
                {
                    /* retrieve data from GPU to CPU */
                    if ((ret = ffmpeg.av_hwframe_transfer_data(sw_frame, frame, 0)) < 0)
                    {
                        Console.WriteLine("Error transferring the data to system memory");
                        goto fail;
                    }

                    tmp_frame = sw_frame;
                }
                else
                {
                    tmp_frame = frame;
                }

                size = ffmpeg.av_image_get_buffer_size((AVPixelFormat)tmp_frame->format, tmp_frame->width,
                                                tmp_frame->height, 1);
                buffer = (byte *)ffmpeg.av_malloc((ulong)size);
                if (buffer == null)
                {
                    Console.WriteLine("Can not alloc buffer");
                    ret = ffmpeg.AVERROR(ffmpeg.ENOMEM);
                    goto fail;
                }

                FFmpeg.AutoGen.byte_ptrArray4* ptrFrameData = (FFmpeg.AutoGen.byte_ptrArray4*)&tmp_frame->data;
                FFmpeg.AutoGen.int_array4* ptrLineSize = (FFmpeg.AutoGen.int_array4*)&tmp_frame->linesize;

                ret = ffmpeg.av_image_copy_to_buffer(buffer, size,
                                              *ptrFrameData, *ptrLineSize,
                                      (AVPixelFormat)tmp_frame->format,
                                      tmp_frame->width, tmp_frame->height, 1);
                if (ret < 0)
                {
                    Console.WriteLine("Can not copy image to buffer");
                    goto fail;
                }

                ReadOnlySpan<byte> contents = new Span<byte>(buffer, size);
                output_file.Write(contents);

            fail:
                ffmpeg.av_frame_free(&frame);
                ffmpeg.av_frame_free(&sw_frame);
                ffmpeg.av_freep(&buffer);
                if (ret < 0)
                {
                    return ret;
                }
            }
        }

        static unsafe int hw_decoder_init(AVCodecContext* ctx, AVHWDeviceType type)
        {
            int err = 0;

            fixed (AVBufferRef** ptr = &_hw_device_ctx)
            {
                if ((err = ffmpeg.av_hwdevice_ctx_create(ptr, type, null, null, 0)) < 0)
                {
                    Console.WriteLine("Failed to create specified HW device.");
                    return err;
                }

                ctx->hw_device_ctx = ffmpeg.av_buffer_ref(*ptr);
            }

            return err;
        }

        static unsafe AVPixelFormat get_hw_format(AVCodecContext* ctx, AVPixelFormat* pix_fmts)
        {
            AVPixelFormat* p;

            for (p = pix_fmts; *p != AVPixelFormat.AV_PIX_FMT_NONE; p++)
            {
                if (*p == _hw_pix_fmt)
                {
                    return *p;
                }
            }

            Console.WriteLine("Failed to get HW surface format.");
            return AVPixelFormat.AV_PIX_FMT_NONE;
        }
    }
}

C#의 unsafe 구문이 없었으면 꿈도 못 꿀 변환입니다. ^^;




그나저나, 지난번 decode_video.c 사례에서는 정상적인 이미지 파일로 변환을 못했는데요, 이번에는 어떨까요? ^^

decode_write 함수에 av_image_copy_to_buffer의 buffer에 담긴 데이터가,

ret = ffmpeg.av_image_copy_to_buffer(buffer, size,
                                *ptrFrameData, *ptrLineSize,
                        (AVPixelFormat)tmp_frame->format,
                        tmp_frame->width, tmp_frame->height, 1);

이거저거 시도해 보니 해석이 됩니다. 가령, 다음과 같이 프레임 하나를 건져 format을 보니 AV_PIX_FMT_NV12로 나옵니다.

if (avctx->frame_number == 310)
{
    if (tmp_frame->format == (int)AVPixelFormat.AV_PIX_FMT_NV12)
    {
        // ...[생략]...
    }
}

AV_PIX_FMT_NV12에 대한 도움말을 보면,

//
// Summary:
//     planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which
//     are interleaved (first byte U and the following byte V)
AV_PIX_FMT_NV12 = 23,

12bit per plane인데, 다행히 이에 대한 해석을 stackoverflow에서 얻을 수 있습니다.

How to Convert NV12 to BGR(AVFrame to cv::Mat)
; https://stackoverflow.com/questions/53994735/how-to-convert-nv12-to-bgravframe-to-cvmat

따라서, 다음과 같이 코딩을 완성할 수 있습니다.

if (avctx->frame_number == 310)
{
    if (tmp_frame->format == (int)AVPixelFormat.AV_PIX_FMT_NV12)
    {
        OpenCvSharp.Mat rgbMat = new OpenCvSharp.Mat();
        OpenCvSharp.Mat nvMat = new OpenCvSharp.Mat(tmp_frame->height * 3 / 2, tmp_frame->width,
            OpenCvSharp.MatType.CV_8UC1, new IntPtr(buffer));

        OpenCvSharp.Cv2.CvtColor(nvMat, rgbMat, OpenCvSharp.ColorConversionCodes.YUV2BGR_NV12);
        OpenCvSharp.Cv2.ImShow("test", rgbMat);
        return ret;
    }
}

8UC1이라 8bit * 3 / 2 = 12bit가 왠지 절묘하게 맞는 것도 같습니다. ^^ 어쨌든, 저렇게 해서 변환된 rgbMat 변수를 OpenCvSharp.Cv2.ImShow로 보면 이미지가 정상적으로 잘 나옵니다. 실제로 아래의 화면에서 곰 플레이어로 본 영상(위)과 OpenCvSharp.Cv2.ImShow로 영상(아래)을 함께 캡처한 것입니다.

(사진은 유튜브 영상 "디에이드"의 "안다은" 님이고 사용을 허락받고 올립니다.)
hw_video_decode_1.png

(첨부 파일은 hw_decode.c 파일의 C#과 Visual C++로 포팅한 프로젝트 파일을 포함합니다.)
(이 글의 소스 코드는 github에 올려져 있습니다.)




그나저나, 정말 영상 압축의 힘이 대단한 것 같습니다. ^^ 13MB 짜리 h264 인코딩된 mp4 파일인데, 이것을 위의 hw_decode.c / decode_write 함수 내에서 해석한 raw 이미지들을 파일로 쓰면 그 용량이 거의 1.8GB에 달합니다.

그리고, AVPixelFormat에 정의된 enum 상수들은 보는 것만으로도 질리게 만듭니다. ^^; 게다가 비슷한 정의들도 있는데, 이번 글에서 언급한 AV_PIX_FMT_NV12 포맷과 AV_PIX_FMT_YUV420P의 포맷은 어떤 차이일까요? ^^ (혹시 아시는 분은 덧글 부탁드립니다.)

AV_PIX_FMT_YUV420P 	
planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)

AV_PIX_FMT_NV12 
planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V)

아마도 AVPixelFormat의 상수를 모두 설명하는 것만으로 책 한 권 분량이 나오지 않을까 싶습니다.




이 예제에서 출력한 test.dat 파일은 다음의 명령어로 재생해 볼 수 있습니다.

ffplay -autoexit -f rawvideo -pixel_format nv12 -video_size 1920x1080 test.dat

참고로, 테스트를 위해 (repo의 Samples 디렉터리에 포함시킨 5.00초 분량의) file_example_MP4_1920_18MG.mp4 파일을 출력한 test.dat 파일을 ffplay로 재생해 보면 5.97초로 재생이 됩니다. 왜냐하면 ffplay는 기본적으로 25fps로 재생하기 때문인데요, 따라서 옵션에 30fps를 지정하면 됩니다.

ffplay -autoexit -f rawvideo -framerate 30 -pixel_format nv12 -video_size 1920x1080 test.dat

그럼 ffplay에 찍히는 재생 시간이 대략 4.96 ~ 4.98초 정도로 나옵니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/14/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2022-01-21 10시14분
[이승준] https://learn.microsoft.com/ko-kr/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering
요기서 yv12랑 nv12를 보시면 됩니다. yv12가 yuv420p 입니다.
[guest]
2022-01-21 09시29분
@이승준 링크 정보 감사합니다. Microsoft 자료에 그렇게 있으리라고는 상상도 못했습니다. ^^ 암튼, 전문 분야는 아니지만 알아가는 재미가 있어서 좋은 것 같습니다.
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13842정성태12/12/2024220디버깅 기술: 205. Windbg - KPCR, KPRCB
13841정성태12/11/2024248오류 유형: 937. error MSB4044: The "ValidateValidArchitecture" task was not given a value for the required parameter "RemoteTarget"
13840정성태12/11/2024232오류 유형: 936. msbuild - Your project file doesn't list 'win' as a "RuntimeIdentifier"
13839정성태12/11/2024241오류 유형: 936. msbuild - error CS1617: Invalid option '12.0' for /langversion. Use '/langversion:?' to list supported values.
13838정성태12/4/20241084오류 유형: 935. Windbg - Breakpoint 0's offset expression evaluation failed.
13837정성태12/3/20241076디버깅 기술: 204. Windbg - 윈도우 핸들 테이블 (3) - Windows 10 이상인 경우
13836정성태12/3/20241104디버깅 기술: 203. Windbg - x64 가상 주소를 물리 주소로 변환 (페이지 크기가 2MB인 경우)
13835정성태12/2/20241076오류 유형: 934. Azure - rm: cannot remove '...': Directory not empty
13834정성태11/29/20241200Windows: 275. C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우)파일 다운로드1
13833정성태11/29/20241224개발 환경 구성: 737. Azure Web App에서 Scale-out으로 늘어난 리눅스 인스턴스에 SSH 접속하는 방법
13832정성태11/27/20241219Windows: 274. Windows 7부터 도입한 conhost.exe
13831정성태11/27/20241019Linux: 111. eBPF - BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_RINGBUF에 대한 다양한 용어들
13830정성태11/25/20241106개발 환경 구성: 736. 파이썬 웹 앱을 Azure App Service에 배포하기
13829정성태11/25/20241050스크립트: 67. 파이썬 - Windows 버전에서 함께 설치되는 py.exe
13828정성태11/25/20241060개발 환경 구성: 735. Azure - 압축 파일을 이용한 web app 배포 시 디렉터리 구분이 안 되는 문제파일 다운로드1
13827정성태11/25/20241147Windows: 273. Windows 환경의 파일 압축 방법 (tar, Compress-Archive)
13826정성태11/21/20241251닷넷: 2313. C# - (비밀번호 등의) Console로부터 입력받을 때 문자열 출력 숨기기(echo 끄기)파일 다운로드1
13825정성태11/21/20241200Linux: 110. eBPF / bpf2go - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
13824정성태11/20/20241160Linux: 109. eBPF / bpf2go - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
13823정성태11/20/20241121개발 환경 구성: 734. Ubuntu에 docker, kubernetes (k3s) 설치
13822정성태11/20/20241084개발 환경 구성: 733. Windbg - VirtualBox VM의 커널 디버거 연결 시 COM 포트가 없는 경우
13821정성태11/18/20241240Linux: 108. Linux와 Windows의 프로세스/스레드 ID 관리 방식
13820정성태11/18/20241256VS.NET IDE: 195. Visual C++ - C# 프로젝트처럼 CopyToOutputDirectory 항목을 추가하는 방법
13819정성태11/15/20241277Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
13818정성태11/15/20241391Windows: 272. Windows 11 24H2 - sudo 추가
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...