视频播放开发笔记-获取MPV的视频内存方式截图

MPV 提供了丰富的各种接口(姑且这么叫吧),其中有个功能是截图功能,使用时,笔者还是颇费了 一番功夫,或许是源于对C语言理解的肤浅。闲话少说,下面先给出文档原文,然后给出代码。


screenshot-raw []
Return a screenshot in memory. This can be used only through the client API. The MPV_FORMAT_NODE_MAP returned by this command has the w, h, stride fields set to obvious contents. The format field is set to bgr0 by default. This format is organized as B8G8R8X8 (where B is the LSB). The contents of the padding X are undefined. The data field is of type MPV_FORMAT_BYTE_ARRAY with the actual image data. The image is freed as soon as the result mpv_node is freed. As usual with client API semantics, you are not allowed to write to the image data.

The stride is the number of bytes from a pixel at (x0, y0) to the pixel at (x0, y0 + 1). This can be larger than w * 4 if the image was cropped, or if there is padding. This number can be negative as well. You access a pixel with byte_index = y * stride + x * 4 (assuming the bgr0 format).

The flags argument is like the first argument to screenshot and supports subtitles, video, window.

注:其他截图功能比较简单,例如保存到文件中这种方式,我们是要放到内存里。

这里的核心部分是对 MPV_FORMAT_NODE_MAP 的理解,我当时还是仔细研读了一下,现在不想再读了。直接给出处理代码部分,请大家对照代码在再结合上文进行理解。

     public Bitmap getScreenShotBmp(string flags)
        {
            if (_mpvHandle == IntPtr.Zero) return null;
            Bitmap bmp;
            int w=0, h=0, stride=0;
            //获得截图数据,byte数组格式
            byte[] imgdat = getScreenShotDat(ref w,ref h, ref stride, flags);
            //将byete数组数据转化成图像
            bmp=imgLittleKit.setImagePixel(imgdat, w, h,stride/w, PixelFormat.Format32bppRgb);
            return bmp;

        }

getScreenShotDat 源代码,也是本文的核心部分

        //flags:subtitles, video, window
        // subtitles and video works well, window :  messy picture got ???
        public unsafe byte[] getScreenShotDat(ref int imgW,ref int imgH,ref int stride ,string flags="video")
        {
            wrong code!
            //if (imgW <= 0 || imgH <= 0) return null;
            /
            byte[] imgDat=null ;
            if (_mpvHandle == IntPtr.Zero) return null;
            try
            {
                mpv_node mpvnode = new mpv_node();

                DoMpvCommandRet(&mpvnode, "screenshot-raw", flags);
                //下面是测试代码....
                //mpv_node *mpvnode;
                //mpvnode = lpBuffer;
                //switch (mpvnode.u.list[0].values[0].u.ba
                //    .
                //{
                //    case mpv_format.MPV_FORMAT_BYTE_ARRAY:
                //    case mpv_format.MPV_FORMAT_DOUBLE:
                //    case mpv_format.MPV_FORMAT_FLAG:
                //    case mpv_format.MPV_FORMAT_INT64:
                //    case mpv_format.MPV_FORMAT_NODE:
                //        Console.WriteLine("The return format is ...");
                //        break;
                //    case mpv_format.MPV_FORMAT_NODE_ARRAY:
                //        Console.WriteLine("The return format is MPV_FORMAT_NODE_ARRAY...");
                //        break;
                //    case mpv_format.MPV_FORMAT_NODE_MAP:
                //        Console.WriteLine("The return format is MPV_FORMAT_NODE_MAP...");
                //        break;

                //    case mpv_format.MPV_FORMAT_NONE:
                //    case mpv_format.MPV_FORMAT_OSD_STRING:
                //    case mpv_format.MPV_FORMAT_STRING:
                //        break;
                //    default:
                //        Console.WriteLine("The return format is an exception...");// 如果没有识别到任何命令,输出一个警告信息
                //        break;
                //}
                
                //based on the debug , find the following
                //Int64 imgW, imgH ;
                
                //获得图像的长宽灯关键数据,代码核心部分
                // 
                if ((int)mpvnode.u.list != 0X0)
                {
                    imgW = (int)mpvnode.u.list[0].values[0].u.int64;//
                    imgH = (int)mpvnode.u.list[0].values[1].u.int64;//
                    stride = (int)mpvnode.u.list[0].values[2].u.int64;//
                    Int64 uknownVal;
                    uknownVal = mpvnode.u.list[0].values[3].u.int64;//

                    //from https://www.cnblogs.com/ljybk/p/9381907.html
                    //C#中,把void*转换为byte
                    // get the image data ;
                    uint imgDatLen = (uint)mpvnode.u.list[0].values[4].u.ba->size;
                    imgDat = new byte[imgDatLen];

                    using (UnmanagedMemoryStream tempUMS = new UnmanagedMemoryStream((byte*)mpvnode.u.list[0].values[4].u.ba->data, imgDatLen))
                    {
                        tempUMS.Read(imgDat, 0, imgDat.Length);
                    }
                    _mpvFreeNodeContents(&mpvnode);
                }
            }
            catch (Exception ex){
                MessageBox.Show(ex.Message);
            }
            
            return imgDat;
        }

setImagePixel 代码,该代码参考了网络代码。

        public static Bitmap setImagePixel(byte[] srcImgDat, int iWidth, int iHeight, int pxlWidth, PixelFormat pxFmt)
        {
            Bitmap dest = new Bitmap(iWidth, iHeight);
            //if (iWidth % 4 != 0)
            //{
            //    iWidth = iWidth - (iWidth % 4);
            //}

            Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
            System.Drawing.Imaging.BitmapData bmpData = dest.LockBits(rect,
                System.Drawing.Imaging.ImageLockMode.ReadWrite, pxFmt);
            IntPtr dstIPtr = bmpData.Scan0;
            int iBytes = iWidth * iHeight * pxlWidth;

            //bmpData.Scan0 = System.Runtime.InteropServices.Marshal.AllocHGlobal(iBytes);  //这句代码必须删除,否则会出现内存问题
            System.Runtime.InteropServices.Marshal.Copy(srcImgDat, 0, dstIPtr, iBytes);

            dest.UnlockBits(bmpData);

            return dest;
        }


有了上面的三个函数,就可以了实现截屏功能了,图像放在内存中。如果有不明白的朋友,可以留言。

注:上述代码仅支持彩色电影,黑白电影有问题,没有仔细思考,估计是图像格式不同造成的,处理应该简单,待本人研究后再做补充。

马拉孙 于 2021-05-08
北京泛五地区

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值