複数の静止画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の作成には使いません。後述します。
#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);
}
#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を作成します。
#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と共にビルドして下さい。
#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
大変参考になり感謝しています。お礼を書いたらお返事までいただき恐縮しています。