Design digital audio systems with real-time Java
This article refers to the address: http://
In the traditional information technology field, Java has become the programming language of choice because it increases developer productivity, software reuse and reliability, reduces software maintenance costs, and provides a more flexible and versatile software architecture.Today, there are a variety of Java solutions in the low-level, hardware, and real-time software arenas. However, when Java technology is applied in very low-level software such as digital audio signal processing, some solutions will be able to take advantage of the advantages of traditional Java technology.
One of these approaches is based on a Java-defined recommendation specification for resource-constrained and security-critical. The main purpose of designing this solution is to maintain the portability, maintainability and scalability advantages of Java. Two computers are included in the information flow within the system, and the two computers operate in concert to exchange audio information over a network communication channel (Figure 1).
The audio signals collected on one node are transmitted to Other nodes and output to the speakers of the remote computer. The audio signal collected on the second node is output in the speaker of the first computer. Conceptually, the information flow is constructed into two separate streams of digital audio data.
This simple audio processing application can be implemented as a PERC Pico application. The software is currently under development and is the first to enable a real-time Java (RTSJ) specification profiling for security-critical and resource-constrained applications. "hard real-time profile" refers to this environment.
Figure 1: A simple digital audio application forms the basis of the critical software engineering discussed in this article. |
Maintenance and scalability requirements
Moore's Law drives the rapid growth in the scale and complexity of typical embedded applications. Competitive pressures are driving software to meet the demands of increasingly powerful hardware. Research on some consumer electronics devices has found that code size growth in new products is very close to Moore's Law, doubling every 18 to 36 months.
About 20 years ago, all the software in each new embedded device was usually written by one or two engineers in less than a year. The development of modern embedded software is very difficult. Assuming that software revisions for new products require hundreds of thousands or even millions of lines of code, the role of embedded software developers will shift more toward how to solve the challenges of integrating many independently developed software components. .
This simple example of digital audio represents a prototype low-level embedded software "product." For most products, the cost of developing the original software is much less than the cost of software maintenance throughout the life of the product. The development of the app during the product life cycle is listed below.
(1) The software will need to be ported to different operating systems and different processing platforms, which will change its CPU time and memory requirements.
(2) The software will be integrated with a variety of complementary functions. Perhaps the next generation of products will also include video signals. Maybe it will support shared digital whiteboards for teleconferencing, or it might be integrated with email and calendar software. Or, some apps may add recording capabilities to save the meeting live on disk.
(3) Two-node network topology may need to be generalized to support meetings with as many participants as possible.
(4) The interface between the analog-to-digital converter (ADC) and the digital signal processor (DSP) can be continuously developed. In some configurations, the operating system provides this service. In other configurations, such an application may include a device driver interface that connects the audio subsystem hardware to the DMA memory device. The audio hardware itself is expected to continue to evolve, which requires software device drivers to evolve.
(5) Network communication protocols may require some changes. In some environments, the software will rely on the underlying operating system services to connect to the network. As various network communication protocols evolve, interfaces to connect operating system network services may even change to provide new QoS parameters and higher bandwidth. In other cases, such an application would need to include a low-level device driver for the hardware interface or a communication protocol stack. Carrier Sense Multiple Access with Collision Detection (CSMA/CD) technology, wireless, fiber optics, and other technologies to be invented, enabling the same in low-cost dedicated serial channels, coaxial cable, and twisted pair data links Basic communication capabilities. Communication libraries may integrate compression, encryption, error detection and correction, as well as sliding window protocols.
The above gives several possible developments of software in commercial business applications. It is not intended to exhaust all the advantages, just to illustrate the benefits of retaining the advantages of Java design, even for some resource-constrained and hard real-time applications. .
Figure 2: The hard real-time JAVA translation environment shows the relationships between tools that make the development, maintenance, and integration of real-time components easier. |
Real-time JAVA capabilities
These real-time Java programming techniques are derived from RTSJ. The specification is very versatile and supports a variety of unique real-time programming requirements. Since this article focuses on very low-level real-time software, we limit the developer's operations to a subset of the full RTSJ specification.
This profiling improves portability, reliability, and efficiency because it prohibits the use of large runtime overheads, non-portable implementation dependencies, and increased software complexity to make programmers more error-prone. Features. Some special differences between hard real-time profiling and full RTSJ include:
(1) The complete RTSJ adopts the priority inheritance method for the synchronization lock and supports the priority limit high simulation optional. Hard real-time profiling prohibits the use of priority inheritance and requires support for priority-limit simulation.
(2) The complete RTSJ allows for immediate modification of various thread scheduling and object synchronization parameters. Hard real-time profiling prohibits immediate adjustments to thread scheduling and synchronization protocols.
(3) The full RTSJ also supports mechanisms that automatically trigger asynchronous events whenever a task misses a deadline or exceeds its CPU time limit. Please note that the implementation of these services is completely non-portable, and accurate execution can result in extremely high runtime overhead. In addition, runtime limits are not required in hard real-time applications because the resource budget and deadline requirements have been statically met before the program is executed. Therefore, hard real-time profiling does not support these mechanisms.
(4) The complete RTSJ supports a mix of traditional threads, real-time threads accessing the garbage collection heap, and real-time threads that do not access the garbage collection heap. This mix of different thread types greatly increases the complexity and scale of the system. This complexity will increase the likelihood of real-time programming errors due to the inability to properly share information between different thread types, and hard real-time profiling only supports real-time threads that do not access the garbage collection heap.
(5) The complete RTSJ provides a library of application programmers to illustrate dynamic memory ranges and allocate objects within a specific range. The use of these libraries is particularly problematic because programmers can create many small errors when developing or integrating components that use nested scopes. To perform the correct scoped-memory usage protocol, RTSJ performs special runtime checks each time the reference field is read and/or rewritten. In a full RTSJ, checking at runtime causes an error in the program component, causing the program to terminate execution in a runtime exception due to illegal allocation, illegal reads, regional memory protocol errors, out of memory errors, and so on. Hard real-time profiling prohibits the use of the RTSJ memory scope operation library. Instead, it requires the programmer to describe its use of scoped-memory in the form of a programmatic comment. These comments can be parsed and executed during compilation, such as the @Scoped and @StaticAnalyzable annotations mentioned in this article.
(6) RTSJ does not standardize libraries for interrupt processing or I/O for low-level devices, while hard real-time profiling defines these libraries.
Commercial pre-implementation implementations of hard real-time profiling show that it runs on some CPU-intensive benchmarks three times faster than standard Java and full RTSJ. This is because the hard real-time execution environment is much simpler than the standard RTSJ, and it also replaces various runtime checks with compile-time validation. This performance is comparable to the corresponding C and C++ programs, and sometimes even better.
Although the use of limited hard real-time profiling is more difficult than using traditional Java, the code development and maintenance of such a platform is easier than the maintenance of the corresponding platform developed in C or C++. This is because the hard real-time Java platform is more portable and provides advanced object-oriented abstraction. In addition, the hard real-time Java platform includes some important development tools that make the development, maintenance, and integration of real-time components easier (Figure 2).
Java development improves reliability and maintainability compared to C and C++ because it includes a bytecode verifier that enforces strict adherence to type safety. C and C++ programmers can take advantage of a variety of mechanisms that make type safety ineffective, and intentional or unintentional exploitation of these vulnerabilities will make the code more prone to errors and reduce portability.
A restricted real-time environment provides more stringent bytecode verification than traditional Java. In particular, the hard real-time validator in Figure 2 ensures that the parameters (pointers) that point to the stack allocation object are no longer than the lifetime of the object itself. It also ensures that program components marked with a dedicated @StaticAnalyzable annotation can be used to restrict the use of Java to the analyzable subset. Integration with a hard real-time translator provides the ability to determine the CPU time and stack memory limit required to execute each component.
All temporary memory allocation required to execute a hard real-time component must be implemented by the runtime stack of the executing thread. Execution starts with a single main thread, while the main thread's runtime stack represents all reusable memory. For each additional thread that is derived from the main thread, it provides a partial runtime stack as the runtime stack for the derived thread.
Figure 3: This block diagram illustrates the architecture of the SimpleAudio digital audio application. |
Implementation of data audio applications
Figure 3 shows the architecture of a digital audio application. It has a total of six threads, including the main thread and the asynchronous event handler represented by the Orchestrator instance. BufferPair connects each socket interface to the corresponding DSP interface. The main thread monitors the user instructions and calls the terminateActivity() method of the SimpleAudio instance when the user requests to close the session. All other threads periodically poll the SimpleAudio instance by calling the continueActivity() service. This method returns a false value when it is shut down.
In the default configuration, the application samples the microphone input at a sampling rate of 8 kHz and acquires 8 bits of data per sample. This configuration produces 8k bytes of digital audio data per second, which is sufficient for simple voice applications. However, it is not suitable for high fidelity stereo signals. A typical CD recording samples 16 bits at a time for two stereo channels at a sampling frequency of 44.1 kHz. The bandwidth requirement for this high fidelity signal is 176.4 kilobytes per second.
In the default configuration, the slot read and write modules use sufficient bandwidth for reliable transmission to reliably provide all data collected from the DSPReader module. We used a direct compression technique in which a series of identical byte values ​​(as they occur during muting) are represented by a dedicated escape value, number of repetitions, and repeated values. Of course, more advanced compression techniques will be more appropriate.
In real-time systems, the jitter describes the expected deviation of the ideal execution time of a particular real-time component, and each component of the digital audio application is described by an exact thread. The SocketWriter thread receives the raw data stream from the DSPReader module, compresses the data, and passes the data to the network socket channel. If the bandwidth of the network socket channel is limited to only 8 kilobytes per second, then any jitter effects that cause the SocketWriter to delay data transmission will accumulate over time.
In the default configuration, SocketWriter is expected to transfer 1 byte of data every 125 μs. If the audio data per second has a delay of 1 byte for half a millisecond, then after 1 hour, the cumulative delay will be approximately 2 seconds. To prevent the accumulation of jitter delays, the architecture includes a monitoring thread running at 16 Hz.
During each cycle, the thread forces the SocketWriter and DSPWriter components to discard data prior to 62.5ms. Since we are dealing with audio data, in general, discarding temporary data values ​​is preferable to allowing data arrival time offsets. People usually don't notice the effect of dropping temporary data bytes.
Note the @StaticAnalyzable comment that appears on line 1 and @ StaticAnalyzable(enforce_time_analysis = {false}, enforce_non_blocking = {false}) in the source list. This represents a partial method signature. Note that this comment gives the parameter values ​​for the enforce_time_analysis and enforce_non_blocking properties, both of which are false. This means that the implementation of the method does not need to limit itself to a subset for which the static analyzer can infer the strict CPU time limit required to execute the method, nor does it require the static analyzer to verify that the method is executed forever. Will not block.
If these attribute definitions are not given, the hard real-time verifier will consider the program to be illegal, because in the source list (!orchestrator.destroy()) { through 57, }, the static analyzer cannot determine how many times the loop contains 55 lines. In addition, the execution of the main method may be blocked between socket_ reader_thread.join() on line 59 and orchestrator_thread.join() on line 63, and await-Termination() called on line 51 in sa.awaitTermination(). Blocked in the method.
What is not noted in the @StaticAnalyzable comment is the value of the enforce_memory_analysis attribute. The default value for this property is true, which means that the implementation of the method must conform to the defined guidelines so that the static parser can determine the maximum amount of memory to allocate when the method is executed. Assuming that the environment's real-time Java rules use memory as part of the run stack, the upper limit of the temporary memory allocation represents the required main thread's runtime stack size.
Comments help with software development and greatly simplify software maintenance. Often, system architects divide complex system functions into smaller components that can be implemented by different development teams. Therefore, the definition of an interface that describes the connections between different components not only specifies the types of parameters that can be passed between components, but also the limitations of real-time processing that must be implemented in each component, reducing the overhead of software maintenance.
Modifications to existing software must follow all other special real-time restrictions described in the component interface comments. If software maintainers violate these interface requirements, they can get direct, unambiguous feedback from the bytecode verifier. This ensures that the ongoing changes in existing large real-time software systems do not shake the stability of existing systems.
When analyzing the stack memory required to reliably run the main program, the static analyzer must determine how much memory each object requires in the method and the method called by the method. To support modular synthesis of static analysis results, the bytecode verifier requires that each method called by the main program be declared as @Static-Analyzable and the enforce_time_analytics property set to true. A quick review of the main method implementation ensures that no allocation occurs within an infinite loop. This is one of the tasks that the bytecode verifier performs.
In the 37th line socket_reader_thread = new Thread-Stack (SocketReader.class); to 41 lines of orchestrator_thread = new ThreadStack (Orchestrator.class) allocated several new ThreadStack objects; each allocation describes the main program derived The stack memory used by the thread. In general, static analysis tools can be difficult to determine the amount of stack memory necessary to reliably execute these child threads.
The argument to each ThreadStack constructor is the class that provides the code to be executed by the corresponding thread. The static analyzer requires that each NoHeapRealtimeThread subclass passed in the environment has a run() method with the @StatAnalyzable annotation and the enforce_ memory_analysis property set to true. If the parameters of the ThreadStack constructor are not from a BoundAsyncEventHandler (for example, in the case of the Orchestrator class), the static parser requires that the asyncEventHandler() method of the class be declared with the @StaticAnalyzable annotation and the enforce_memory_analysis property set to true.
The current thread's runtime stack can satisfy all temporary memory needs. Note that we allocated two temporary BufferPair instances on line 23, microphone_stream = new BufferPair(); on line 24, speaker_stream = new Buffer-Pair(); then the parameters of these objects are passed to the constructor, For each thread that contains different functional components of the software application. One of the limitations of the hard real-time validator implementation is that the parameters of the stack-allocated object cannot survive longer than the object itself that references the parameter, as well as by the annotation mechanism. Let's take a look at the constructor of the SocketReader class:
@ScopedPure
@StaticAnalyzable(enforce_time_analysis = {false}, enforce_non_blocking = {false})
SocketReader(SimpleAudio sa, Buffer-Pair buffers, String socket_name) throws
FileNotFoundException
The @ScopedPure annotation indicates that each input parameter of the constructor can refer to objects that are located in the runtime stack of the external nested scope. The bytecode verifier ensures that the contents of these parameters are never copied to variables that are not equally distinguished due to the @Scoped assignment.
In addition, it prohibits copying the values ​​of internal nested scope variables to external nested scope variables. An exception to this is that in a special environment, it can prove that the parameterized object is in the same or more outer scope than the value to be assigned. If the argument to this constructor is not specified by the @Scoped annotation, the bytecode validator will not allow the main program to pass arguments to the BufferPair and SimpleAudio objects of the stack allocation.
One of the real-time programming abstractions supported by RTSJ in this application is the PeriodTimer class. Note that this application illustrates the PeriodicTimer object on line 49, drumbeat = new PeriodicTimer(start_time, period, orchestrator); and assigns the result to the local drumbeat variable. One of the parameters is the reference parameter of the orchestrator object, which is itself an instance of BoundAsyncEventHandler. The drumbeat cycle timer is set to trigger the handleAsyncEvent() method of the orchestrator object every 16 times, ie every 62.5 microseconds.
Real-time developers in C or C++ can implement many of the same constructs supported by these real-time Java technologies. However, C or C++ programmers must generate dangling pointers and memory leaks, and they lack the support of standard tools to automatically analyze execution time and stack size.
In addition, C and C++ programmers lack integrity checks to ensure that method implementations meet the requirements of a documented real-time interface and that method calls can pass parameters that also meet the requirements of the document interface. Finally, in the maintenance of the current software system, C and C++ programmers do not have the tool support to ensure that modifications to existing software are consistent with the various component requirements assumed in the original software development process.
Traditional Java has many advantages in terms of productivity and cost. The standard use of real-time Java technology offers many of these advantages. Compared to using C and C++, a typical Java programmer has twice the productivity during new code development and 5 to 10 times productivity during current software maintenance. As the size and complexity of embedded real-time software increases, these factors that have spurred the transition to more modern software engineering technologies, such as those implemented in real-time Java, have become increasingly important.
LED Tunnel Light,Tunnel Light,LED Tunnel Lamp
Aixuan Lighting & Technology Co., Ltd. , http://www.eko-lamps.com