C++ 如何使用模式从内存复制字节(YUYV打包到YUV420平面)

C++ 如何使用模式从内存复制字节(YUYV打包到YUV420平面),c++,memory-management,video-processing,micro-optimization,pixelformat,C++,Memory Management,Video Processing,Micro Optimization,Pixelformat,让我们从这个开始: 我有一个16字节的内存块,我只需要将偶数字节复制到一个8字节的内存块中 我当前的算法是这样做的: unsigned int source_size = 16, destination_size = 8, i; unsigned char * source = new unsigned char[source_size]; unsigned char * destination = new unsigned char[destination_size]; // fill s

让我们从这个开始:

我有一个16字节的内存块,我只需要将偶数字节复制到一个8字节的内存块中

我当前的算法是这样做的:

unsigned int source_size = 16, destination_size = 8, i;

unsigned char * source = new unsigned char[source_size];
unsigned char * destination = new unsigned char[destination_size];

// fill source
for( i = 0; i < source_size; ++i)
{
    source[i] = 0xf + i;
}
// source :
// 0f 10 11 12  13 14 15 16  17 18 19 1a  1b 1c 1d 1e

// copy
for( i = 0; i < destination_size; ++i)
{
    destination[i] = source[i * 2];
}
// destination :
// 0f 11 13 15  17 19 1b 1d
unsigned int source_size=16,destination_size=8,i;
unsigned char*source=新的unsigned char[source_size];
无符号字符*目的地=新的无符号字符[目的地大小];
//填充源
对于(i=0;i
这只是一个例子,因为我想知道当我需要获取每3个字节或每4个字节,而不仅仅是偶数字节时,是否有更好的方法来实现这一点

我知道使用循环我可以做到这一点,但我需要选择这个。。。我不知道如何使用SSE,所以我不知道在这种情况下是否可以使用,但是像memcpy magic之类的东西会很棒

我还考虑过使用宏来消除循环,因为源和目标的大小都是恒定的,但这看起来没什么大不了的


如果我说这是提取YUYV像素格式的YCbCr字节,也许你可以开箱思考。另外,我需要强调的是,我这样做是为了摆脱libswscale。

不幸的是,您不能仅使用
memcpy()
技巧。现代处理器有64位寄存器,它是内存传输的最佳大小。现代编译器总是尝试优化
memcpy()
调用,一次进行64(或32,甚至128)位的传输

但在您的情况下,您需要“奇怪的”24或16位传输。这就是为什么我们有SSE、NEON和其他处理器扩展。这就是为什么它们被广泛应用于视频处理


因此,在您的情况下,您应该使用一个SSE优化的LIB,或者编写自己的汇编代码来执行此内存传输。

不幸的是,您不能仅使用
memcpy()
技巧来执行此操作。现代处理器有64位寄存器,它是内存传输的最佳大小。现代编译器总是尝试优化
memcpy()
调用,一次进行64(或32,甚至128)位的传输

但在您的情况下,您需要“奇怪的”24或16位传输。这就是为什么我们有SSE、NEON和其他处理器扩展。这就是为什么它们被广泛应用于视频处理


因此,在您的情况下,您应该使用一个SSE优化的LIB或编写自己的汇编代码来进行内存传输。

虽然我怀疑编译器和cpu在这种情况下已经做得很好了;如果你真的想要替代品,那就研究一下反转莫顿数的技术。这个问题展示了如何在位上实现它,但是这个想法也可以扩展到字节

类似(仅示例,这不是生产质量)

//假设目标已为零。。。
对于(int i=0;i*pD |=*pS |(*pS虽然我怀疑编译器和cpu在这种情况下已经做得很好了;如果你真的想要替代品,请研究反转莫顿数的技术。这个问题展示了如何在位上做这件事,但这个想法也可以扩展到字节

类似(仅示例,这不是生产质量)

//假设目标已为零。。。
对于(int i=0;i*pD |=*pS |(*pS此问题可通过SSSE3有效解决:


这种方法应该非常快,特别是当您进行许多这样的转换时。它只需要一条洗牌指令(不计算加载/存储),这似乎是有代价的。请注意,这种方法也可以用于其他字节模式。

使用SSSE3可以有效地解决此问题:


这种方法应该很快,特别是当你做很多这样的转换时。它只需要一条洗牌指令(不包括加载/存储),这似乎有。请注意,这种方法也可以用于其他字节模式。

它受到内存带宽的限制,为什么要麻烦呢?它受到内存带宽的限制,为什么要麻烦呢?我必须每16个字节做一次这样的操作吗?谢谢分享。@RafaelLucio:是的,每16个字节的输入只做一次操作。我必须这样做吗每16个字节执行一次?谢谢分享。@RafaelLucio:是的,每16个字节的输入只执行一次操作。
// assuming destination is already zero...
For (int i=0; i < destination_size; i += 2) {
   long* pS = (long*) &source[ i * 2 ];
   long* pD = (long*) &destination[ i ];
   long a = *pS &0xff00ff00;
   *pD |= *pS | ( *pS << 8 );
}
#include <tmmintrin.h>  //SSSE3 and before
...
//source must be 16-byte aligned
unsigned char * source = (unsigned char *)_mm_malloc(source_size, 16);
//destination must be 8-byte aligned (that's natural anyway)
unsigned char * destination = (unsigned char *)_mm_malloc(destination_size, 8);
...
__m128i mask = _mm_set_epi8(                        //shuffling control mask (constant)
    -1, -1, -1, -1, -1, -1, -1, -1, 14, 12, 10, 8, 6, 4, 2, 0
);
__m128i reg = *(const __m128i*)source;              //load 16-bit register
__m128i comp = _mm_shuffle_epi8(reg, mask);         //do the bytes compaction
_mm_storel_epi64((__m128i*)destination, comp);      //store lower 64 bits
movdqa  xmm0, XMMWORD PTR [rsi]
pshufb  xmm0, XMMWORD PTR __xmm@ffffffffffffffff0e0c0a0806040200
movq    QWORD PTR [rax], xmm0