emmel
15th September 2006, 17:30
Hello,
Inspired by the great work of Tobias Bergmann, the author of ELDER (see http://forum.doom9.org/showthread.php?t=102119 ), I wrote another small script for parallel usage of x264. I call it "x264_tptool.exe" or "trivial parallel encoding tool for x264".
I'd like to point out that there is nothing Elder can't do this script can. On the contrary - Elder is much more sophisticated and capable of supporting almost all x264 command line switches, whereas this particular script is limited to one-pass encoding. Yet, I wrote it cause I needed something as simple and robust as possible.
The idea is simple (as in the first stage of Elder): The script divides all frames of a clip uniformly into "chunks", and encodes them in parallel by multiple x264-threads. It scales almost linearly up to 32 cores (haven't tested more), if the number of chunks is at least 2..4 times the number of threds. Even with only 2 cores, it is usually about 15% faster than the native "--threads 2" option of x264. The technique behind the script is of course embarrasingly trivial with respect to that of x264.
Usage is simple. If you have an x264 command line, e.g.
x264 --threads 2 --thread-input -b 2 -r 4 --me dia -o c.mp4 c.avs
just replace "x264" by "x264_tptool" and add two switches:
x264_tptool --tp-chunks 8 --tp-threads 2 -b 2 -r 4 --me dia -o c.mp4 c.avs
The switch "--tp-chunks" defines the total number of frame-chunks, and "--tp-threads" the total number of parallel threads for encoding them.
Limitations:
- Only one pass encoding
- Only AviSynth input
- Only mp4 output
- x264 and MP4Box must be in path
- [it must be possible to add "trim()" in the end of the original AviSynt script] <-- [edit: removed]
I'd finally like to point out that the motivation behind the script is pure simplicity. If you need more advaced features, or more accurate load balancing, please check Elder instead.
Thank you, and sorry if this is something too trivial to be discussed on this forum.
The binary file "x264_tptool.exe" is available from http://www.mytempdir.com/932989 [edit: link updated] Source:
//--------------------------------------------------------------------------------
// x264_tptool: trivial parallel encoding tool for x264
//
// Divides the frames of a clip into chunks of (almost) equal size, and
// encodes them in multiple parallel threads (instances of x264). Scales
// quasi-linearly if the number of chunks (N) is greater than the number
// of threads (M), say N > 2..4 * M. Some rules of thumb:
//
// - Choose N small with respect to M, if you prefer better rate control
// - Choose N big with respect to N, if you prefer better scaling
//
// All verbose output directed to stderr
//
// Usage:
//
// - Command line arguments as in x264
// - Additional arguments:
// --tp-threads int: number of parallel threads
// --tp-chunks int: total number of frame chunks
//
// Sample:
//
// x264_tptool --tp-threads 4 --tp-chunks 40 --thread-input \
// --crf 22 --me dia -b 2 -r 2 -o clip.mp4 clip.avs
//
// Assumptions:
//
// - x264.exe must be in path (not checked)
// - MP4Box.exe must be in path (not checked)
//
// Limitations:
//
// - Only 1-pass encoding
// - Only AviSynth input
// - Only mp4 output
// - Script name must be the last argument in command line
//
// Compile under MinGW with gcc. Link to libvfw32:
// gcc x264_tptool.c -O -o x264_tptool.exe -lvfw32
//
// Written by emmel, 16. Sept. 2006
//--------------------------------------------------------------------------------
#include <stdio.h>
#include <math.h>
#include <windows.h>
#include <vfw.h>
#define MAXTHREADS 32
#define MAXSTRLEN 2048
#define RELAX 1 // sec
#undef TEST
DWORD WINAPI LaunchEncoder( LPVOID lpParam );
//--------------------------------------------------------------------------------
int main( int argc, char **argv ) {
HANDLE threads[MAXTHREADS];
DWORD ExitCode;
int start, end, thread, chunk, done = 0;
char avs_cmd[MAXSTRLEN], avs_name[MAXSTRLEN];
char x264_cmd[MAXSTRLEN], MP4Box_cmd[MAXSTRLEN];
char x264_cmd_line[MAXSTRLEN];
AVISTREAMINFO avs_info;
PAVISTREAM ppavi;
// Parse command line arguments:
//------------------------------
memset( x264_cmd_line, '\0', MAXSTRLEN );
char *avs, *mp4;
int chunks = 0;
int num_threads = 0;
while( *++argv ) {
if( !strncmp( *argv, "--tp-threads", 12 ) ) {
num_threads = atoi( *++argv );
} else if( !strncmp( *argv, "--tp-chunks", 11 ) ) {
chunks = atoi( *++argv );
} else if( !strncmp( *argv, "--output", 8 ) ) {
mp4 = *++argv;
} else if( !strncmp( *argv, "-o", 2 ) ) {
mp4 = *++argv;
} else {
avs = *argv; // script name must be the last argument
if( *( argv+1 ) != NULL )
sprintf( x264_cmd_line, "%s %s", x264_cmd_line, *argv );
}
}
if( num_threads < 1 || num_threads > MAXTHREADS ) {
fprintf( stderr, "Illegal number of threads (max=%d).\n", MAXTHREADS );
return 0;
}
// Let AviSynth report the total number of frames:
//------------------------------------------------
AVIFileInit( );
if( AVIStreamOpenFromFile( &ppavi, avs, streamtypeVIDEO, 0, OF_READ, NULL ) ) {
fprintf( stderr, "Unable to open avisynth script %s\n", avs );
AVIFileExit( );
return 0;
}
if( AVIStreamInfo( ppavi, &avs_info, sizeof(AVISTREAMINFO ) ) ) {
fprintf( stderr, "Unable to retrieve stream info\n" );
AVIStreamRelease( ppavi );
AVIFileExit( );
return 0;
}
AVIStreamRelease( ppavi );
AVIFileExit( );
int frames = avs_info.dwLength;
if( frames < 1 ) {
fprintf( stderr, "No frames to encode\n" );
return 0;
}
if( chunks < 1 || chunks > frames ) {
fprintf( stderr, "Too few or too many chunks\n" );
return 0;
}
int chunk_size = (int)ceil( (double)frames/(double)chunks );
fprintf( stderr, "Script : %s\n", avs );
fprintf( stderr, "Frames : %d\n", frames );
fprintf( stderr, "Chunks : %d\n", chunks );
fprintf( stderr, "Chunk size : %d\n", chunk_size );
fprintf( stderr, "Threads : %d\n", num_threads );
fprintf( stderr, "Output : %s\n", mp4 );
fprintf( stderr, "x264 switches :%s\n", x264_cmd_line );
// Loop over chunks:
//------------------
for( thread = 0; thread < num_threads; thread++ ) threads[thread] = NULL;
fprintf( stderr, "Start encoding:\n" );
int next_chunk = 1;
while( !done ) {
for( thread = 0; thread < num_threads; thread++ ) {
GetExitCodeThread( threads[thread], &ExitCode );
if( !( ExitCode == STILL_ACTIVE ) && !done ) {
fprintf( stderr, "\n[x264_tptool] Thread %d ready to go: ", thread+1 );
start = (next_chunk-1) * chunk_size + 1;
end = next_chunk * chunk_size;
if( end >= frames ) {
end = frames;
done = 1; // last
}
fprintf( stderr, "encoding range %d...%d:\n", start, end );
// Launch encoder:
//----------------
sprintf( x264_cmd, "x264%s --seek %d --frames %d -o chunk%d.264 %s &",
x264_cmd_line, start, chunk_size, next_chunk, avs );
fprintf( stderr, "%s\n\n", x264_cmd );
threads[thread] = CreateThread( NULL, 0, LaunchEncoder, &x264_cmd, 0, NULL );
Sleep( RELAX );
next_chunk++;
fflush( stderr );
}
}
Sleep( RELAX );
}
// Ok. Wait for completion:
//-------------------------
Sleep( RELAX );
WaitForMultipleObjects( num_threads, threads, TRUE, INFINITE );
fprintf( stderr, "Done!\n" );
// Finally, concatenate results (scalar):
//---------------------------------------
#if defined TEST
fprintf( stderr, "Concatenate results...\n" );
#else
fprintf( stderr, "\nConcatenating results (%d chunks):\n", next_chunk-1 );
for( chunk = 1; chunk < next_chunk; chunk++ ) {
fprintf( stderr, "\nConcatenating chunk %d to %s:\n", chunk, mp4 );
fflush( stderr );
sprintf( MP4Box_cmd, "MP4Box -cat chunk%d.264 %s", chunk, mp4 );
system( MP4Box_cmd );
}
#endif
fprintf( stderr, "All done!\n" );
return 0;
}
//--------------------------------------------------------------------------------
DWORD WINAPI LaunchEncoder( LPVOID lpParam ) {
char *DataString = (char*)lpParam;
#if defined TEST
fprintf( stderr, "Launch encoder...\n" );
fflush( stderr );
#else
system( DataString );
#endif
return 0;
}
Inspired by the great work of Tobias Bergmann, the author of ELDER (see http://forum.doom9.org/showthread.php?t=102119 ), I wrote another small script for parallel usage of x264. I call it "x264_tptool.exe" or "trivial parallel encoding tool for x264".
I'd like to point out that there is nothing Elder can't do this script can. On the contrary - Elder is much more sophisticated and capable of supporting almost all x264 command line switches, whereas this particular script is limited to one-pass encoding. Yet, I wrote it cause I needed something as simple and robust as possible.
The idea is simple (as in the first stage of Elder): The script divides all frames of a clip uniformly into "chunks", and encodes them in parallel by multiple x264-threads. It scales almost linearly up to 32 cores (haven't tested more), if the number of chunks is at least 2..4 times the number of threds. Even with only 2 cores, it is usually about 15% faster than the native "--threads 2" option of x264. The technique behind the script is of course embarrasingly trivial with respect to that of x264.
Usage is simple. If you have an x264 command line, e.g.
x264 --threads 2 --thread-input -b 2 -r 4 --me dia -o c.mp4 c.avs
just replace "x264" by "x264_tptool" and add two switches:
x264_tptool --tp-chunks 8 --tp-threads 2 -b 2 -r 4 --me dia -o c.mp4 c.avs
The switch "--tp-chunks" defines the total number of frame-chunks, and "--tp-threads" the total number of parallel threads for encoding them.
Limitations:
- Only one pass encoding
- Only AviSynth input
- Only mp4 output
- x264 and MP4Box must be in path
- [it must be possible to add "trim()" in the end of the original AviSynt script] <-- [edit: removed]
I'd finally like to point out that the motivation behind the script is pure simplicity. If you need more advaced features, or more accurate load balancing, please check Elder instead.
Thank you, and sorry if this is something too trivial to be discussed on this forum.
The binary file "x264_tptool.exe" is available from http://www.mytempdir.com/932989 [edit: link updated] Source:
//--------------------------------------------------------------------------------
// x264_tptool: trivial parallel encoding tool for x264
//
// Divides the frames of a clip into chunks of (almost) equal size, and
// encodes them in multiple parallel threads (instances of x264). Scales
// quasi-linearly if the number of chunks (N) is greater than the number
// of threads (M), say N > 2..4 * M. Some rules of thumb:
//
// - Choose N small with respect to M, if you prefer better rate control
// - Choose N big with respect to N, if you prefer better scaling
//
// All verbose output directed to stderr
//
// Usage:
//
// - Command line arguments as in x264
// - Additional arguments:
// --tp-threads int: number of parallel threads
// --tp-chunks int: total number of frame chunks
//
// Sample:
//
// x264_tptool --tp-threads 4 --tp-chunks 40 --thread-input \
// --crf 22 --me dia -b 2 -r 2 -o clip.mp4 clip.avs
//
// Assumptions:
//
// - x264.exe must be in path (not checked)
// - MP4Box.exe must be in path (not checked)
//
// Limitations:
//
// - Only 1-pass encoding
// - Only AviSynth input
// - Only mp4 output
// - Script name must be the last argument in command line
//
// Compile under MinGW with gcc. Link to libvfw32:
// gcc x264_tptool.c -O -o x264_tptool.exe -lvfw32
//
// Written by emmel, 16. Sept. 2006
//--------------------------------------------------------------------------------
#include <stdio.h>
#include <math.h>
#include <windows.h>
#include <vfw.h>
#define MAXTHREADS 32
#define MAXSTRLEN 2048
#define RELAX 1 // sec
#undef TEST
DWORD WINAPI LaunchEncoder( LPVOID lpParam );
//--------------------------------------------------------------------------------
int main( int argc, char **argv ) {
HANDLE threads[MAXTHREADS];
DWORD ExitCode;
int start, end, thread, chunk, done = 0;
char avs_cmd[MAXSTRLEN], avs_name[MAXSTRLEN];
char x264_cmd[MAXSTRLEN], MP4Box_cmd[MAXSTRLEN];
char x264_cmd_line[MAXSTRLEN];
AVISTREAMINFO avs_info;
PAVISTREAM ppavi;
// Parse command line arguments:
//------------------------------
memset( x264_cmd_line, '\0', MAXSTRLEN );
char *avs, *mp4;
int chunks = 0;
int num_threads = 0;
while( *++argv ) {
if( !strncmp( *argv, "--tp-threads", 12 ) ) {
num_threads = atoi( *++argv );
} else if( !strncmp( *argv, "--tp-chunks", 11 ) ) {
chunks = atoi( *++argv );
} else if( !strncmp( *argv, "--output", 8 ) ) {
mp4 = *++argv;
} else if( !strncmp( *argv, "-o", 2 ) ) {
mp4 = *++argv;
} else {
avs = *argv; // script name must be the last argument
if( *( argv+1 ) != NULL )
sprintf( x264_cmd_line, "%s %s", x264_cmd_line, *argv );
}
}
if( num_threads < 1 || num_threads > MAXTHREADS ) {
fprintf( stderr, "Illegal number of threads (max=%d).\n", MAXTHREADS );
return 0;
}
// Let AviSynth report the total number of frames:
//------------------------------------------------
AVIFileInit( );
if( AVIStreamOpenFromFile( &ppavi, avs, streamtypeVIDEO, 0, OF_READ, NULL ) ) {
fprintf( stderr, "Unable to open avisynth script %s\n", avs );
AVIFileExit( );
return 0;
}
if( AVIStreamInfo( ppavi, &avs_info, sizeof(AVISTREAMINFO ) ) ) {
fprintf( stderr, "Unable to retrieve stream info\n" );
AVIStreamRelease( ppavi );
AVIFileExit( );
return 0;
}
AVIStreamRelease( ppavi );
AVIFileExit( );
int frames = avs_info.dwLength;
if( frames < 1 ) {
fprintf( stderr, "No frames to encode\n" );
return 0;
}
if( chunks < 1 || chunks > frames ) {
fprintf( stderr, "Too few or too many chunks\n" );
return 0;
}
int chunk_size = (int)ceil( (double)frames/(double)chunks );
fprintf( stderr, "Script : %s\n", avs );
fprintf( stderr, "Frames : %d\n", frames );
fprintf( stderr, "Chunks : %d\n", chunks );
fprintf( stderr, "Chunk size : %d\n", chunk_size );
fprintf( stderr, "Threads : %d\n", num_threads );
fprintf( stderr, "Output : %s\n", mp4 );
fprintf( stderr, "x264 switches :%s\n", x264_cmd_line );
// Loop over chunks:
//------------------
for( thread = 0; thread < num_threads; thread++ ) threads[thread] = NULL;
fprintf( stderr, "Start encoding:\n" );
int next_chunk = 1;
while( !done ) {
for( thread = 0; thread < num_threads; thread++ ) {
GetExitCodeThread( threads[thread], &ExitCode );
if( !( ExitCode == STILL_ACTIVE ) && !done ) {
fprintf( stderr, "\n[x264_tptool] Thread %d ready to go: ", thread+1 );
start = (next_chunk-1) * chunk_size + 1;
end = next_chunk * chunk_size;
if( end >= frames ) {
end = frames;
done = 1; // last
}
fprintf( stderr, "encoding range %d...%d:\n", start, end );
// Launch encoder:
//----------------
sprintf( x264_cmd, "x264%s --seek %d --frames %d -o chunk%d.264 %s &",
x264_cmd_line, start, chunk_size, next_chunk, avs );
fprintf( stderr, "%s\n\n", x264_cmd );
threads[thread] = CreateThread( NULL, 0, LaunchEncoder, &x264_cmd, 0, NULL );
Sleep( RELAX );
next_chunk++;
fflush( stderr );
}
}
Sleep( RELAX );
}
// Ok. Wait for completion:
//-------------------------
Sleep( RELAX );
WaitForMultipleObjects( num_threads, threads, TRUE, INFINITE );
fprintf( stderr, "Done!\n" );
// Finally, concatenate results (scalar):
//---------------------------------------
#if defined TEST
fprintf( stderr, "Concatenate results...\n" );
#else
fprintf( stderr, "\nConcatenating results (%d chunks):\n", next_chunk-1 );
for( chunk = 1; chunk < next_chunk; chunk++ ) {
fprintf( stderr, "\nConcatenating chunk %d to %s:\n", chunk, mp4 );
fflush( stderr );
sprintf( MP4Box_cmd, "MP4Box -cat chunk%d.264 %s", chunk, mp4 );
system( MP4Box_cmd );
}
#endif
fprintf( stderr, "All done!\n" );
return 0;
}
//--------------------------------------------------------------------------------
DWORD WINAPI LaunchEncoder( LPVOID lpParam ) {
char *DataString = (char*)lpParam;
#if defined TEST
fprintf( stderr, "Launch encoder...\n" );
fflush( stderr );
#else
system( DataString );
#endif
return 0;
}