0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

複数のjpeg静止画からMotionJpeg動画を作る

Posted at

複数の静止画jpegファイルをMotionJpegのフォーマットで保存するとMediaPlayerなどで再生可能な動画ファイルを作成することが出来ます。

mpegと違い個々のフレームは独立しているので、MotionJpegファイルから個々の静止画を抽出することも出来ます。

作り方を工夫すると、リアルタイムでMotionJpegの作成が可能です。
筆者はwebカメラで一定間隔で取得し続ける画像をGUIへ貼り付けると共にMotionJpeg化しています。

概要

AVI形式のMotionJpegは大別するとヘッダー・本体・フッターの3つで構成しています。

ヘッダーは、今回のような限定的な使い方であれば固定長でもOKのようです。
(本体が完成するまで分からない)ファイルの数なども記録するので、本体作成前は空のヘッダー領域を確保するだけです。本体作成後に内容を書き込みます。

本体はヘッダーの後ろに作成します。MotionJpeg用の情報8バイトに続けてjpegファイルを丸ごとコピーする繰り返しです。その際、各jpegファイルの先頭アドレスを覚えておきます。

フッターは本体完成後に本体の後ろに作成します。各jpegファイルがMotionJpegファイル内のどこから始まっているかを記録しています。

上述の理由から、MotionJpegは次の手順で作成します。
  1. ヘッダー領域確保
  2. 本体作成、各jpeg先頭アドレス記録
  3. ヘッダーおよびフッター作成

サンプルコード

必要な処理と変数をまとめてクラス化しました。LinuxでもWindowsでも動きます。最後の2つの関数はMotionJpegの作成には使いません。後述します。

LocalMjpg.cpp
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "LocalMjpg.h"

#define LDEF_MJPG_HEADER_SIZE       0x100
#define LDEF_JPG_BITCOUNT           24

typedef struct {
	uint8_t bId0;
	uint8_t bId1;
	uint8_t bId2;
	uint8_t bId3;
	uint32_t dwKeyRane;
	uint32_t dwOffset;
	uint32_t dwFileSize;
} tagFooterInfo;
tagFooterInfo FI;


CLocalMjpg::CLocalMjpg()
{
	CP = new tagClassParameter;
}


CLocalMjpg::~CLocalMjpg()
{
	delete CP;
}


void CLocalMjpg::Init(int nWidth, int nHeight, int nFps, char *bpFnMjpg)
{
	memset(CP, 0, sizeof(tagClassParameter));
	(CP->nWidth) = nWidth;
	(CP->nHeight) = nHeight;
    (CP->nFps) = nFps;
	sprintf((CP->sFnMjpg), "%s", bpFnMjpg);

    uint8_t b1 = 0;
    FILE *fp = fopen((CP->sFnMjpg), "wb");                                  // delete ex temp file
	fwrite(&b1, sizeof(b1), LDEF_MJPG_HEADER_SIZE, fp);						// reserve header area
    fclose(fp);
}


void CLocalMjpg::AddJpgToArray(char *bpFnJpg)
{
	static unsigned char sId[] = {'0', '0', 'd', 'c'};
    FILE *fpS, *fpD;
    uint32_t lLenJ, lLenM;

    fpS = fopen(bpFnJpg, "rb");
    if (fpS==NULL) {
        return;
    }
    fseek(fpS, 0, SEEK_END);
    lLenJ = ftell(fpS);
    fseek(fpS, 0, SEEK_SET);

    fpD = fopen((CP->sFnMjpg), "ab");
    if(fpD==NULL) {
        fclose(fpS);
        return;
    }

	fwrite(sId, sizeof(sId), 1, fpD);

    lLenM = lLenJ + (lLenJ % 2 ? 1 : 0);
    fwrite(&lLenM, sizeof(lLenM), 1, fpD);

    //----------------------------------------------- heap
    unsigned char *bpHp = new unsigned char [lLenM];
    //----------------------------------------------- heap
    memset(bpHp, 0, lLenM);
    fread(bpHp, lLenJ, 1, fpS);
    fwrite(bpHp, lLenM, 1, fpD);
    //----------------------------------------------- heap
    delete[] bpHp;
    //----------------------------------------------- heap

    fclose(fpD);
    fclose(fpS);

    (CP->nsJpgFileSize[CP->nNumJpgFile]) = (int)lLenM;
    (CP->nAllJpgFileSize) += (int)lLenM;
    (CP->nNumJpgFile) ++;
}


void CLocalMjpg::Finalize(void)
{
	CreateMjpgHeader();
	CreateMjpgFooter();
}


void CLocalMjpg::CreateMjpgHeader(void)
{
	uint32_t dw1;
	uint16_t us1;
	unsigned char sHd[LDEF_MJPG_HEADER_SIZE];

	memset(sHd, 0, sizeof(sHd));
	
	sHd[0x00] = 'R';
	sHd[0x01] = 'I';
	sHd[0x02] = 'F';
	sHd[0x03] = 'F';

	dw1 = 256 + (CP->nAllJpgFileSize) + 8 * (CP->nNumJpgFile) + 16 * (CP->nNumJpgFile);
	memcpy(&(sHd[0x04]), &dw1, sizeof(dw1));

	sHd[0x08] = 'A';
	sHd[0x09] = 'V';
	sHd[0x0A] = 'I';
	sHd[0x0B] = ' ';

	sHd[0x0C] = 'L';
	sHd[0x0D] = 'I';
	sHd[0x0E] = 'S';
	sHd[0x0F] = 'T';

	dw1 = 0xE0;
	memcpy(&(sHd[0x10]), &dw1, sizeof(dw1));

	sHd[0x14] = 'h';
	sHd[0x15] = 'd';
	sHd[0x16] = 'r';
	sHd[0x17] = 'l';

	sHd[0x18] = 'a';
	sHd[0x19] = 'v';
	sHd[0x1A] = 'i';
	sHd[0x1B] = 'h';

	dw1 = 0x38;
	memcpy(&(sHd[0x1C]), &dw1, sizeof(dw1));

	dw1 = (uint32_t)(1000000 / (CP->nFps));					// MicroSecPerFrame
	memcpy(&(sHd[0x20]), &dw1, sizeof(dw1));

    dw1 = 7000;												// MaxBytesPerSec  7000 = 0x1B58
	memcpy(&(sHd[0x24]), &dw1, sizeof(dw1));

	dw1 = 16;												// Flags
	memcpy(&(sHd[0x2C]), &dw1, sizeof(dw1));

    dw1 = (uint32_t)(CP->nNumJpgFile);						// TotalFrames
	memcpy(&(sHd[0x30]), &dw1, sizeof(dw1));

    dw1 = 1;												// Streams
	memcpy(&(sHd[0x38]), &dw1, sizeof(dw1));

	dw1 = (CP->nWidth);										// Width
	memcpy(&(sHd[0x40]), &dw1, sizeof(dw1));

	dw1 = (CP->nHeight);									// Height
	memcpy(&(sHd[0x44]), &dw1, sizeof(dw1));

	sHd[0x58] = 'L';
	sHd[0x59] = 'I';
	sHd[0x5A] = 'S';
	sHd[0x5B] = 'T';

	dw1 = 148;
	memcpy(&(sHd[0x5C]), &dw1, sizeof(dw1));

	sHd[0x60] = 's';
	sHd[0x61] = 't';
	sHd[0x62] = 'r';
	sHd[0x63] = 'l';

	sHd[0x64] = 's';
	sHd[0x65] = 't';
	sHd[0x66] = 'r';
	sHd[0x67] = 'h';

	dw1 = 64;
	memcpy(&(sHd[0x68]), &dw1, sizeof(dw1));

	sHd[0x6C] = 'v';
	sHd[0x6D] = 'i';
	sHd[0x6E] = 'd';
	sHd[0x6F] = 's';

	sHd[0x70] = 'M';
	sHd[0x71] = 'J';
	sHd[0x72] = 'P';
	sHd[0x73] = 'G';

	dw1 = 1;												// Scale
	memcpy(&(sHd[0x80]), &dw1, sizeof(dw1));

	dw1 = (uint32_t)(CP->nFps);								// fps
	memcpy(&(sHd[0x84]), &dw1, sizeof(dw1));

	dw1 = (uint32_t)(CP->nNumJpgFile);						// number of jpegs
	memcpy(&(sHd[0x8C]), &dw1, sizeof(dw1));

	dw1 = (CP->nWidth);                                   	// jpgs_width
	memcpy(&(sHd[0xA4]), &dw1, sizeof(dw1));
	dw1 = (CP->nHeight);                                    // jpgs_height
	memcpy(&(sHd[0xA8]), &dw1, sizeof(dw1));

	sHd[0xAC] = 's';
	sHd[0xAD] = 't';
	sHd[0xAE] = 'r';
	sHd[0xAF] = 'f';

	dw1 = 40;
	memcpy(&(sHd[0xB0]), &dw1, sizeof(dw1));

    dw1 = 40;
	memcpy(&(sHd[0xB4]), &dw1, sizeof(dw1));

    dw1 = (CP->nWidth);										// jpgs_width;
	memcpy(&(sHd[0xB8]), &dw1, sizeof(dw1));

	dw1 = (CP->nHeight);									// jpgs_height;
	memcpy(&(sHd[0xBC]), &dw1, sizeof(dw1));

	us1 = 1;												// Planes  WORD
	memcpy(&(sHd[0xC0]), &us1, sizeof(us1));
	us1 = LDEF_JPG_BITCOUNT;								// BitCount  WORD
	memcpy(&(sHd[0xC2]), &us1, sizeof(us1));

	sHd[0xC4] = 'M';										// Compression = 'MJPG';
	sHd[0xC5] = 'J';
	sHd[0xC6] = 'P';
	sHd[0xC7] = 'G';
															// SizeImage
    dw1 = (((CP->nWidth) * LDEF_JPG_BITCOUNT / 8 + 3) & 0xFFFFFFFC) * (CP->nHeight);
	memcpy(&(sHd[0xC8]), &dw1, sizeof(dw1));
    
	sHd[0xDC] = 'L';
	sHd[0xDD] = 'I';
	sHd[0xDE] = 'S';
	sHd[0xDF] = 'T';

	dw1 = 16;
	memcpy(&(sHd[0xE0]), &dw1, sizeof(dw1));

	sHd[0xE4] = 'o';
	sHd[0xE5] = 'd';
	sHd[0xE6] = 'm';
	sHd[0xE7] = 'l';

	sHd[0xE8] = 'd';
	sHd[0xE9] = 'm';
	sHd[0xEA] = 'l';
	sHd[0xEB] = 'h';

	dw1 = 4;												// szs = 4;
	memcpy(&(sHd[0xEC]), &dw1, sizeof(dw1));

	dw1 = (uint32_t)(CP->nNumJpgFile);						// nbr_of_jpgs
	memcpy(&(sHd[0xF0]), &dw1, sizeof(dw1));

	sHd[0xF4] = 'L';
	sHd[0xF5] = 'I';
	sHd[0xF6] = 'S';
	sHd[0xF7] = 'T';

    dw1 = (uint32_t)((CP->nAllJpgFileSize) + 4 + 8 * (CP->nNumJpgFile));     // Size = len + 4 + 8 * nbr_of_jpgs;
	memcpy(&(sHd[0xF8]), &dw1, sizeof(dw1));
    
	sHd[0xFC] = 'm';
	sHd[0xFD] = 'o';
	sHd[0xFE] = 'v';
	sHd[0xFF] = 'i';

	//----------------
    FILE *fp;
    fp = fopen((CP->sFnMjpg), "r+b");
    if (fp==NULL) {
        return;
    }
    fseek(fp, 0, SEEK_SET);
    fwrite(sHd, sizeof(sHd), 1, fp);
	fclose(fp);
}


void CLocalMjpg::CreateMjpgFooter(void)
{
	static unsigned char sId[] = {'i', 'd', 'x', '1'};
    FILE *fp;

    fp = fopen((CP->sFnMjpg), "ab");
    if(fp==NULL) {
        return;
    }

	fwrite(sId, sizeof(sId), 1, fp);

    uint32_t index_length = 4 * 4 * (CP->nNumJpgFile);
    fwrite(&index_length, sizeof(index_length), 1, fp);

    (FI.bId0) = '0';
    (FI.bId1) = '0';
    (FI.bId2) = 'd';
    (FI.bId3) = 'c';
    (FI.dwKeyRane) = 16;
    (FI.dwOffset) = 4;

    for(int nLoop=0; nLoop<(CP->nNumJpgFile); nLoop++) {
        (FI.dwFileSize) = (uint32_t)(CP->nsJpgFileSize[nLoop]);
        fwrite(&FI, sizeof(tagFooterInfo), 1, fp);
        //--------
        (FI.dwOffset) += (FI.dwFileSize);
        (FI.dwOffset) += 8;
    }

    fclose(fp);
}


int CLocalMjpg::GetNumJpgInMjpg(char *bpFn)
{
    int nRtn = 0;
    uint32_t dw1;
    FILE *fp;

    fp = fopen(bpFn, "rb");
    if (fp==NULL) {
        return(nRtn);
    }
    fseek(fp, 0x30, SEEK_SET);
    fread(&dw1, sizeof(dw1), 1, fp);
    fclose(fp);

    nRtn = (int)dw1;
    return(nRtn);
}


// nPos : top=0 bottom=(NumJpeg-1)
int CLocalMjpg::GetJpgInMjpg(char *bpFnJpg, char *bpFnMjpg, int nPos)
{
    int nJpgPos, nJpgSize;
    tagFooterInfo FI;

    //-------- get num jpg
    int nNumJpg = GetNumJpgInMjpg(bpFnMjpg);
    if(nNumJpg<=nPos) {
        return(-1);
    }
    int n1 = (nNumJpg * 0x10);
    n1 -= (nPos * 0x10);
    int nOffset = 0 - n1;

    //-------- get offset
    FILE *fp = fopen(bpFnMjpg, "rb");
    if (fp==NULL) {
        return(-1);
    }
    fseek(fp, nOffset, SEEK_END);
    fread(&FI, sizeof(tagFooterInfo), 1, fp);
    nJpgSize = (int)(FI.dwFileSize);
    nJpgPos = (FI.dwOffset);
    nJpgPos -= 4;
    nJpgPos += 8;
    nJpgPos += LDEF_MJPG_HEADER_SIZE;
    //-------- get jpg in mjpg
    //-------------------------------------------------------- heap
    unsigned char *bpHp = new unsigned char [nJpgSize];
    //-------------------------------------------------------- heap
    fseek(fp, nJpgPos, SEEK_SET);
    fread(bpHp, nJpgSize, 1, fp);
    if((bpHp[nJpgSize-1])==0) {
        nJpgSize --;
    }
    fclose(fp);
    //-------- copy to jpg file
    fp = fopen(bpFnJpg, "wb");
    fwrite(bpHp, nJpgSize, 1, fp);
    fclose(fp);
    //-------------------------------------------------------- heap
    delete [] bpHp;
    //-------------------------------------------------------- heap
    return(nJpgSize);
}
LocalMjpg.h
#ifndef CLOCALMJPG_H_INCLUDED
#define CLOCALMJPG_H_INCLUDED

class CLocalMjpg
{
	public:
		CLocalMjpg();
		virtual ~CLocalMjpg();
		void Init(int nWidth, int nHeight, int nFps, char *bpFnMjpg);
		void AddJpgToArray(char *bpFnJpg);
		void Finalize(void);
		int GetNumJpgInMjpg(char *bpFn);
		int GetJpgInMjpg(char *bpFnJpg, char *bpFnMjpg, int nPos);

	private:
		typedef struct {
			int nWidth;
			int nHeight;
			int nFps;
		    int nNumJpgFile;
    		int nsJpgFileSize[10100];
	    	int nAllJpgFileSize;
		    char sPgmPath[256];
    		char sFnMjpg[256];
		} tagClassParameter;
		tagClassParameter *CP;
		//--------
		void CreateMjpgHeader(void);
		void CreateMjpgFooter(void);
};

#endif // #ifndef CLOCALMJPG_H_INCLUDED

実行例

000.jpg〜009.jpgという名前を付けた640x480pixelの10個のjpegファイルを用意します。webカメラの画像などが良いと思います。画像ソフトでjpegファイルを作っても良いですが、gimpなどで設定可能なProgressiveJpegでは保存しないようご注意下さい(再生出来ない動画ソフトがあります)。

次のコードで、上記の10ファイルを2fps(=500msec間隔)で再生するMotionJpegを作成します。

main.cpp
#include <stdio.h>
#include "LocalMjpg.h"


int main(int argc, char *argv[])
{
	char sFn[64];
	CLocalMjpg *lmp = new CLocalMjpg;
	
	(lmp->Init)(640, 480, 2, (char *)"test.avi");

	for(int nLoop=0; nLoop<10; nLoop++) {
		sprintf(sFn, "%03d.jpg", nLoop);
		(lmp->AddJpgToArray)(sFn);
	}

	(lmp->Finalize)();
	delete lmp;
	return(0);
}

LinuxでもWindowsでもビルド可能です。Linuxの場合のビルド例を記します。

g++ -o mjpgtest main.cpp LocalMjpg.cpp

出来上がったバイナリー(mjpgtest)を10個のjpegファイルと同じパスへコピーして実行するとtest.aviというMotionJpegファイルが出来上がります。MediaPlayerなどで再生出来ると思います。

MotionJpegからjpegを抽出する

さきほど出来たtest.aviから個々のjpegファイルを抽出するサンプルコードもご紹介します。上記と同様にLocalMjpg.cppと共にビルドして下さい。

main.cpp
#include <stdio.h>
#include "LocalMjpg.h"


int main(int argc, char *argv[])
{
	char sFnJpeg[64], sFnMj[64];

	CLocalMjpg *lmp = new CLocalMjpg;
		
	sprintf(sFnMj, "test.avi");
	int nQty = (lmp->GetNumJpgInMjpg)(sFnMj);
	printf("number of jpeg in %s = %d\n", sFnMj, nQty);

	for(int nLoop=0; nLoop<nQty; nLoop++) {
		sprintf(sFnJpeg, "%03d.jpg", nLoop);
		printf("%s\n", sFnJpeg);
		(lmp->GetJpgInMjpg)(sFnJpeg, sFnMj, nLoop);
	}

	delete lmp;
	return(0);
}
$ ./mjpgtest 
number of jpeg in test.avi = 10
000.jpg
001.jpg
002.jpg
003.jpg
004.jpg
005.jpg
006.jpg
007.jpg
008.jpg
009.jpg

謝辞

MotionJpegに関する実践的な情報がなかなか見つからず、やっとたどり着いたのがこちらのサイトです。
  Ricardicus/mjpeg-avi
大変参考になり感謝しています。お礼を書いたらお返事までいただき恐縮しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?